1 /* 2 * Copyright (C) 2008 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 17 package com.android.settingslib.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.content.Context; 22 import android.util.Log; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 26 import java.util.ArrayList; 27 import java.util.Collection; 28 import java.util.List; 29 import java.util.Objects; 30 31 /** 32 * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. 33 */ 34 public class CachedBluetoothDeviceManager { 35 private static final String TAG = "CachedBluetoothDeviceManager"; 36 private static final boolean DEBUG = BluetoothUtils.D; 37 38 private Context mContext; 39 private final LocalBluetoothManager mBtManager; 40 41 @VisibleForTesting 42 final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); 43 @VisibleForTesting 44 HearingAidDeviceManager mHearingAidDeviceManager; 45 CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager)46 CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { 47 mContext = context; 48 mBtManager = localBtManager; 49 mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices); 50 } 51 getCachedDevicesCopy()52 public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { 53 return new ArrayList<>(mCachedDevices); 54 } 55 onDeviceDisappeared(CachedBluetoothDevice cachedDevice)56 public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { 57 cachedDevice.setJustDiscovered(false); 58 return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE; 59 } 60 onDeviceNameUpdated(BluetoothDevice device)61 public void onDeviceNameUpdated(BluetoothDevice device) { 62 CachedBluetoothDevice cachedDevice = findDevice(device); 63 if (cachedDevice != null) { 64 cachedDevice.refreshName(); 65 } 66 } 67 68 /** 69 * Search for existing {@link CachedBluetoothDevice} or return null 70 * if this device isn't in the cache. Use {@link #addDevice} 71 * to create and return a new {@link CachedBluetoothDevice} for 72 * a newly discovered {@link BluetoothDevice}. 73 * 74 * @param device the address of the Bluetooth device 75 * @return the cached device object for this device, or null if it has 76 * not been previously seen 77 */ findDevice(BluetoothDevice device)78 public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) { 79 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 80 if (cachedDevice.getDevice().equals(device)) { 81 return cachedDevice; 82 } 83 // Check sub devices if it exists 84 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 85 if (subDevice != null && subDevice.getDevice().equals(device)) { 86 return subDevice; 87 } 88 } 89 90 return null; 91 } 92 93 /** 94 * Create and return a new {@link CachedBluetoothDevice}. This assumes 95 * that {@link #findDevice} has already been called and returned null. 96 * @param device the address of the new Bluetooth device 97 * @return the newly created CachedBluetoothDevice object 98 */ addDevice(BluetoothDevice device)99 public CachedBluetoothDevice addDevice(BluetoothDevice device) { 100 LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); 101 CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, profileManager, 102 device); 103 mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice); 104 synchronized (this) { 105 if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { 106 mCachedDevices.add(newDevice); 107 mBtManager.getEventManager().dispatchDeviceAdded(newDevice); 108 } 109 } 110 111 return newDevice; 112 } 113 114 /** 115 * Returns device summary of the pair of the hearing aid passed as the parameter. 116 * 117 * @param CachedBluetoothDevice device 118 * @return Device summary, or if the pair does not exist or if it is not a hearing aid, 119 * then {@code null}. 120 */ getSubDeviceSummary(CachedBluetoothDevice device)121 public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) { 122 CachedBluetoothDevice subDevice = device.getSubDevice(); 123 if (subDevice != null && subDevice.isConnected()) { 124 return subDevice.getConnectionSummary(); 125 } 126 return null; 127 } 128 129 /** 130 * Search for existing sub device {@link CachedBluetoothDevice}. 131 * 132 * @param device the address of the Bluetooth device 133 * @return true for found sub device or false. 134 */ isSubDevice(BluetoothDevice device)135 public synchronized boolean isSubDevice(BluetoothDevice device) { 136 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 137 if (!cachedDevice.getDevice().equals(device)) { 138 // Check sub devices if it exists 139 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 140 if (subDevice != null && subDevice.getDevice().equals(device)) { 141 return true; 142 } 143 } 144 } 145 return false; 146 } 147 148 /** 149 * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the 150 * Hearing Aid Service is connected and the HiSyncId's are now available. 151 * @param LocalBluetoothProfileManager profileManager 152 */ updateHearingAidsDevices()153 public synchronized void updateHearingAidsDevices() { 154 mHearingAidDeviceManager.updateHearingAidsDevices(); 155 } 156 157 /** 158 * Attempts to get the name of a remote device, otherwise returns the address. 159 * 160 * @param device The remote device. 161 * @return The name, or if unavailable, the address. 162 */ getName(BluetoothDevice device)163 public String getName(BluetoothDevice device) { 164 CachedBluetoothDevice cachedDevice = findDevice(device); 165 if (cachedDevice != null && cachedDevice.getName() != null) { 166 return cachedDevice.getName(); 167 } 168 169 String name = device.getAliasName(); 170 if (name != null) { 171 return name; 172 } 173 174 return device.getAddress(); 175 } 176 clearNonBondedDevices()177 public synchronized void clearNonBondedDevices() { 178 clearNonBondedSubDevices(); 179 mCachedDevices.removeIf(cachedDevice 180 -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE); 181 } 182 clearNonBondedSubDevices()183 private void clearNonBondedSubDevices() { 184 for (int i = mCachedDevices.size() - 1; i >= 0; i--) { 185 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); 186 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 187 if (subDevice != null 188 && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { 189 // Sub device exists and it is not bonded 190 cachedDevice.setSubDevice(null); 191 } 192 } 193 } 194 onScanningStateChanged(boolean started)195 public synchronized void onScanningStateChanged(boolean started) { 196 if (!started) return; 197 // If starting a new scan, clear old visibility 198 // Iterate in reverse order since devices may be removed. 199 for (int i = mCachedDevices.size() - 1; i >= 0; i--) { 200 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); 201 cachedDevice.setJustDiscovered(false); 202 final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 203 if (subDevice != null) { 204 subDevice.setJustDiscovered(false); 205 } 206 } 207 } 208 onBluetoothStateChanged(int bluetoothState)209 public synchronized void onBluetoothStateChanged(int bluetoothState) { 210 // When Bluetooth is turning off, we need to clear the non-bonded devices 211 // Otherwise, they end up showing up on the next BT enable 212 if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { 213 for (int i = mCachedDevices.size() - 1; i >= 0; i--) { 214 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); 215 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); 216 if (subDevice != null) { 217 if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) { 218 cachedDevice.setSubDevice(null); 219 } 220 } 221 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { 222 cachedDevice.setJustDiscovered(false); 223 mCachedDevices.remove(i); 224 } 225 } 226 } 227 } 228 onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile)229 public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, 230 int bluetoothProfile) { 231 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 232 boolean isActive = Objects.equals(cachedDevice, activeDevice); 233 cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile); 234 } 235 } 236 onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state)237 public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice 238 cachedDevice, int state) { 239 return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, 240 state); 241 } 242 onDeviceUnpaired(CachedBluetoothDevice device)243 public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { 244 CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device); 245 CachedBluetoothDevice subDevice = device.getSubDevice(); 246 if (subDevice != null) { 247 // Main device is unpaired, to unpair sub device 248 subDevice.unpair(); 249 device.setSubDevice(null); 250 } else if (mainDevice != null) { 251 // Sub device unpaired, to unpair main device 252 mainDevice.unpair(); 253 mainDevice.setSubDevice(null); 254 } 255 } 256 dispatchAudioModeChanged()257 public synchronized void dispatchAudioModeChanged() { 258 for (CachedBluetoothDevice cachedDevice : mCachedDevices) { 259 cachedDevice.onAudioModeChanged(); 260 } 261 } 262 log(String msg)263 private void log(String msg) { 264 if (DEBUG) { 265 Log.d(TAG, msg); 266 } 267 } 268 } 269