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 (mainDevice != null) { 184 // When main device exists, receiving sub device disconnection 185 // To update main device UI 186 mainDevice.refresh(); 187 return true; 188 } 189 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 190 if (subDevice != null && subDevice.isConnected()) { 191 // Main device is disconnected and sub device is connected 192 // To copy data from sub device to main device 193 mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); 194 cachedDevice.switchSubDeviceContent(); 195 cachedDevice.refresh(); 196 // It is necessary to do remove and add for updating the mapping on 197 // preference and device 198 mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); 199 return true; 200 } 201 break; 202 } 203 return false; 204 } 205 findMainDevice(CachedBluetoothDevice device)206 CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { 207 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 208 if (isValidHiSyncId(cachedDevice.getHiSyncId())) { 209 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 210 if (subDevice != null && subDevice.equals(device)) { 211 return cachedDevice; 212 } 213 } 214 } 215 return null; 216 } 217 log(String msg)218 private void log(String msg) { 219 if (DEBUG) { 220 Log.d(TAG, msg); 221 } 222 } 223 }