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