/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.List; import java.util.Set; /** * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices. */ public class HearingAidDeviceManager { private static final String TAG = "HearingAidDeviceManager"; private static final boolean DEBUG = BluetoothUtils.D; private final LocalBluetoothManager mBtManager; private final List mCachedDevices; HearingAidDeviceManager(LocalBluetoothManager localBtManager, List CachedDevices) { mBtManager = localBtManager; mCachedDevices = CachedDevices; } void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) { long hiSyncId = getHiSyncId(newDevice.getDevice()); if (isValidHiSyncId(hiSyncId)) { // Once hiSyncId is valid, assign hiSyncId newDevice.setHiSyncId(hiSyncId); } } private long getHiSyncId(BluetoothDevice device) { LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); HearingAidProfile profileProxy = profileManager.getHearingAidProfile(); if (profileProxy != null) { return profileProxy.getHiSyncId(device); } return BluetoothHearingAid.HI_SYNC_ID_INVALID; } boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) { final long hiSyncId = newDevice.getHiSyncId(); if (isValidHiSyncId(hiSyncId)) { final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId); // Just add one of the hearing aids from a pair in the list that is shown in the UI. // Once there is another device with the same hiSyncId, to add new device as sub // device. if (hearingAidDevice != null) { hearingAidDevice.setSubDevice(newDevice); return true; } } return false; } private boolean isValidHiSyncId(long hiSyncId) { return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; } private CachedBluetoothDevice getCachedDevice(long hiSyncId) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getHiSyncId() == hiSyncId) { return cachedDevice; } } return null; } // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId void updateHearingAidsDevices() { final Set newSyncIdSet = new HashSet(); for (CachedBluetoothDevice cachedDevice : mCachedDevices) { // Do nothing if HiSyncId has been assigned if (!isValidHiSyncId(cachedDevice.getHiSyncId())) { final long newHiSyncId = getHiSyncId(cachedDevice.getDevice()); // Do nothing if there is no HiSyncId on Bluetooth device if (isValidHiSyncId(newHiSyncId)) { cachedDevice.setHiSyncId(newHiSyncId); newSyncIdSet.add(newHiSyncId); } } } for (Long syncId : newSyncIdSet) { onHiSyncIdChanged(syncId); } } // Group devices by hiSyncId @VisibleForTesting void onHiSyncIdChanged(long hiSyncId) { int firstMatchedIndex = -1; for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getHiSyncId() != hiSyncId) { continue; } if (firstMatchedIndex == -1) { // Found the first one firstMatchedIndex = i; continue; } // Found the second one int indexToRemoveFromUi; CachedBluetoothDevice subDevice; CachedBluetoothDevice mainDevice; // Since the hiSyncIds have been updated for a connected pair of hearing aids, // we remove the entry of one the hearing aids from the UI. Unless the // hiSyncId get updated, the system does not know it is a hearing aid, so we add // both the hearing aids as separate entries in the UI first, then remove one // of them after the hiSyncId is populated. We will choose the device that // is not connected to be removed. if (cachedDevice.isConnected()) { mainDevice = cachedDevice; indexToRemoveFromUi = firstMatchedIndex; subDevice = mCachedDevices.get(firstMatchedIndex); } else { mainDevice = mCachedDevices.get(firstMatchedIndex); indexToRemoveFromUi = i; subDevice = cachedDevice; } mainDevice.setSubDevice(subDevice); mCachedDevices.remove(indexToRemoveFromUi); log("onHiSyncIdChanged: removed from UI device =" + subDevice + ", with hiSyncId=" + hiSyncId); mBtManager.getEventManager().dispatchDeviceRemoved(subDevice); break; } } // @return {@code true}, the event is processed inside the method. It is for updating // hearing aid device on main-sub relationship when receiving connected or disconnected. // @return {@code false}, it is not hearing aid device or to process it same as other profiles boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state) { switch (state) { case BluetoothProfile.STATE_CONNECTED: onHiSyncIdChanged(cachedDevice.getHiSyncId()); CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice); if (mainDevice != null){ if (mainDevice.isConnected()) { // When main device exists and in connected state, receiving sub device // connection. To refresh main device UI mainDevice.refresh(); return true; } else { // When both Hearing Aid devices are disconnected, receiving sub device // connection. To switch content and dispatch to notify UI change mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); mainDevice.switchSubDeviceContent(); mainDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); return true; } } break; case BluetoothProfile.STATE_DISCONNECTED: mainDevice = findMainDevice(cachedDevice); if (mainDevice != null) { // When main device exists, receiving sub device disconnection // To update main device UI mainDevice.refresh(); return true; } CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.isConnected()) { // Main device is disconnected and sub device is connected // To copy data from sub device to main device mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); cachedDevice.switchSubDeviceContent(); cachedDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); return true; } break; } return false; } CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (isValidHiSyncId(cachedDevice.getHiSyncId())) { CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.equals(device)) { return cachedDevice; } } } return null; } private void log(String msg) { if (DEBUG) { Log.d(TAG, msg); } } }