1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settingslib.bluetooth; 17 18 import android.bluetooth.BluetoothDevice; 19 import android.bluetooth.BluetoothHearingAid; 20 import android.bluetooth.BluetoothProfile; 21 import android.util.Log; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.util.HashSet; 26 import java.util.List; 27 import java.util.Set; 28 29 /** 30 * HearingAidDeviceManager manages the set of remote HearingAid Bluetooth devices. 31 */ 32 public class HearingAidDeviceManager { 33 private static final String TAG = "HearingAidDeviceManager"; 34 private static final boolean DEBUG = BluetoothUtils.D; 35 36 private final LocalBluetoothManager mBtManager; 37 private final List<CachedBluetoothDevice> mCachedDevices; HearingAidDeviceManager(LocalBluetoothManager localBtManager, List<CachedBluetoothDevice> CachedDevices)38 HearingAidDeviceManager(LocalBluetoothManager localBtManager, 39 List<CachedBluetoothDevice> CachedDevices) { 40 mBtManager = localBtManager; 41 mCachedDevices = CachedDevices; 42 } 43 initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice)44 void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) { 45 long hiSyncId = getHiSyncId(newDevice.getDevice()); 46 if (isValidHiSyncId(hiSyncId)) { 47 // Once hiSyncId is valid, assign hiSyncId 48 newDevice.setHiSyncId(hiSyncId); 49 } 50 } 51 getHiSyncId(BluetoothDevice device)52 private long getHiSyncId(BluetoothDevice device) { 53 LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); 54 HearingAidProfile profileProxy = profileManager.getHearingAidProfile(); 55 if (profileProxy != null) { 56 return profileProxy.getHiSyncId(device); 57 } 58 return BluetoothHearingAid.HI_SYNC_ID_INVALID; 59 } 60 setSubDeviceIfNeeded(CachedBluetoothDevice newDevice)61 boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) { 62 final long hiSyncId = newDevice.getHiSyncId(); 63 if (isValidHiSyncId(hiSyncId)) { 64 final CachedBluetoothDevice hearingAidDevice = getCachedDevice(hiSyncId); 65 // Just add one of the hearing aids from a pair in the list that is shown in the UI. 66 // Once there is another device with the same hiSyncId, to add new device as sub 67 // device. 68 if (hearingAidDevice != null) { 69 hearingAidDevice.setSubDevice(newDevice); 70 return true; 71 } 72 } 73 return false; 74 } 75 isValidHiSyncId(long hiSyncId)76 private boolean isValidHiSyncId(long hiSyncId) { 77 return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; 78 } 79 getCachedDevice(long hiSyncId)80 private CachedBluetoothDevice getCachedDevice(long hiSyncId) { 81 for (int i = mCachedDevices.size() - 1; i >= 0; i--) { 82 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); 83 if (cachedDevice.getHiSyncId() == hiSyncId) { 84 return cachedDevice; 85 } 86 } 87 return null; 88 } 89 90 // To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId updateHearingAidsDevices()91 void updateHearingAidsDevices() { 92 final Set<Long> newSyncIdSet = new HashSet<Long>(); 93 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 94 // Do nothing if HiSyncId has been assigned 95 if (!isValidHiSyncId(cachedDevice.getHiSyncId())) { 96 final long newHiSyncId = getHiSyncId(cachedDevice.getDevice()); 97 // Do nothing if there is no HiSyncId on Bluetooth device 98 if (isValidHiSyncId(newHiSyncId)) { 99 cachedDevice.setHiSyncId(newHiSyncId); 100 newSyncIdSet.add(newHiSyncId); 101 } 102 } 103 } 104 for (Long syncId : newSyncIdSet) { 105 onHiSyncIdChanged(syncId); 106 } 107 } 108 109 // Group devices by hiSyncId 110 @VisibleForTesting onHiSyncIdChanged(long hiSyncId)111 void onHiSyncIdChanged(long hiSyncId) { 112 int firstMatchedIndex = -1; 113 114 for (int i = mCachedDevices.size() - 1; i >= 0; i--) { 115 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); 116 if (cachedDevice.getHiSyncId() != hiSyncId) { 117 continue; 118 } 119 if (firstMatchedIndex == -1) { 120 // Found the first one 121 firstMatchedIndex = i; 122 continue; 123 } 124 // Found the second one 125 int indexToRemoveFromUi; 126 CachedBluetoothDevice subDevice; 127 CachedBluetoothDevice mainDevice; 128 // Since the hiSyncIds have been updated for a connected pair of hearing aids, 129 // we remove the entry of one the hearing aids from the UI. Unless the 130 // hiSyncId get updated, the system does not know it is a hearing aid, so we add 131 // both the hearing aids as separate entries in the UI first, then remove one 132 // of them after the hiSyncId is populated. We will choose the device that 133 // is not connected to be removed. 134 if (cachedDevice.isConnected()) { 135 mainDevice = cachedDevice; 136 indexToRemoveFromUi = firstMatchedIndex; 137 subDevice = mCachedDevices.get(firstMatchedIndex); 138 } else { 139 mainDevice = mCachedDevices.get(firstMatchedIndex); 140 indexToRemoveFromUi = i; 141 subDevice = cachedDevice; 142 } 143 144 mainDevice.setSubDevice(subDevice); 145 mCachedDevices.remove(indexToRemoveFromUi); 146 log("onHiSyncIdChanged: removed from UI device =" + subDevice 147 + ", with hiSyncId=" + hiSyncId); 148 mBtManager.getEventManager().dispatchDeviceRemoved(subDevice); 149 break; 150 } 151 } 152 153 // @return {@code true}, the event is processed inside the method. It is for updating 154 // hearing aid device on main-sub relationship when receiving connected or disconnected. 155 // @return {@code false}, it is not hearing aid device or to process it same as other profiles onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state)156 boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, 157 int state) { 158 switch (state) { 159 case BluetoothProfile.STATE_CONNECTED: 160 onHiSyncIdChanged(cachedDevice.getHiSyncId()); 161 CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice); 162 if (mainDevice != null){ 163 if (mainDevice.isConnected()) { 164 // When main device exists and in connected state, receiving sub device 165 // connection. To refresh main device UI 166 mainDevice.refresh(); 167 return true; 168 } else { 169 // When both Hearing Aid devices are disconnected, receiving sub device 170 // connection. To switch content and dispatch to notify UI change 171 mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); 172 mainDevice.switchSubDeviceContent(); 173 mainDevice.refresh(); 174 // It is necessary to do remove and add for updating the mapping on 175 // preference and device 176 mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); 177 return true; 178 } 179 } 180 break; 181 case BluetoothProfile.STATE_DISCONNECTED: 182 mainDevice = findMainDevice(cachedDevice); 183 if (cachedDevice.getUnpairing()) { 184 return true; 185 } 186 if (mainDevice != null) { 187 // When main device exists, receiving sub device disconnection 188 // To update main device UI 189 mainDevice.refresh(); 190 return true; 191 } 192 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 193 if (subDevice != null && subDevice.isConnected()) { 194 // Main device is disconnected and sub device is connected 195 // To copy data from sub device to main device 196 mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); 197 cachedDevice.switchSubDeviceContent(); 198 cachedDevice.refresh(); 199 // It is necessary to do remove and add for updating the mapping on 200 // preference and device 201 mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); 202 return true; 203 } 204 break; 205 } 206 return false; 207 } 208 findMainDevice(CachedBluetoothDevice device)209 CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { 210 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 211 if (isValidHiSyncId(cachedDevice.getHiSyncId())) { 212 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 213 if (subDevice != null && subDevice.equals(device)) { 214 return cachedDevice; 215 } 216 } 217 } 218 return null; 219 } 220 log(String msg)221 private void log(String msg) { 222 if (DEBUG) { 223 Log.d(TAG, msg); 224 } 225 } 226 }