blob: 15f6983a684c327bea61fa12f516de492f2e1d28 [file] [log] [blame]
Jason Monk7ce96b92015-02-02 11:27:58 -05001/*
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
17package com.android.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
Isha Bobrac3d94132018-02-08 16:04:36 -080021import android.bluetooth.BluetoothHearingAid;
Jason Monk7ce96b92015-02-02 11:27:58 -050022import android.content.Context;
23import android.util.Log;
24
Isha Bobrac3d94132018-02-08 16:04:36 -080025import com.android.internal.annotations.VisibleForTesting;
26
Jason Monk7ce96b92015-02-02 11:27:58 -050027import java.util.ArrayList;
28import java.util.Collection;
Isha Bobrac3d94132018-02-08 16:04:36 -080029import java.util.HashMap;
Stanley Tng16a56902018-04-20 11:54:36 -070030import java.util.HashSet;
Jason Monk7ce96b92015-02-02 11:27:58 -050031import java.util.List;
Isha Bobrac3d94132018-02-08 16:04:36 -080032import java.util.Map;
Stanley Tng16a56902018-04-20 11:54:36 -070033import java.util.Set;
Pavlin Radoslavovc285d552018-02-06 16:14:00 -080034import java.util.Objects;
Jason Monk7ce96b92015-02-02 11:27:58 -050035
36/**
37 * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
38 */
Fan Zhang82dd3b02016-12-27 13:13:00 -080039public class CachedBluetoothDeviceManager {
Jason Monk7ce96b92015-02-02 11:27:58 -050040 private static final String TAG = "CachedBluetoothDeviceManager";
41 private static final boolean DEBUG = Utils.D;
42
43 private Context mContext;
Jason Monkbe3c5db2015-02-04 13:00:55 -050044 private final LocalBluetoothManager mBtManager;
Jason Monk7ce96b92015-02-02 11:27:58 -050045
Isha Bobrac3d94132018-02-08 16:04:36 -080046 @VisibleForTesting
47 final List<CachedBluetoothDevice> mCachedDevices =
48 new ArrayList<CachedBluetoothDevice>();
49 // Contains the list of hearing aid devices that should not be shown in the UI.
50 @VisibleForTesting
51 final List<CachedBluetoothDevice> mHearingAidDevicesNotAddedInCache
52 = new ArrayList<CachedBluetoothDevice>();
53 // Maintains a list of devices which are added in mCachedDevices and have hiSyncIds.
54 @VisibleForTesting
55 final Map<Long, CachedBluetoothDevice> mCachedDevicesMapForHearingAids
56 = new HashMap<Long, CachedBluetoothDevice>();
57
Jason Monkbe3c5db2015-02-04 13:00:55 -050058 CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
Jason Monk7ce96b92015-02-02 11:27:58 -050059 mContext = context;
Jason Monkbe3c5db2015-02-04 13:00:55 -050060 mBtManager = localBtManager;
Jason Monk7ce96b92015-02-02 11:27:58 -050061 }
62
63 public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
64 return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
65 }
66
67 public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
Jack He51520472017-07-24 12:30:08 -070068 cachedDevice.setJustDiscovered(false);
Jason Monk7ce96b92015-02-02 11:27:58 -050069 return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
70 }
71
72 public void onDeviceNameUpdated(BluetoothDevice device) {
73 CachedBluetoothDevice cachedDevice = findDevice(device);
74 if (cachedDevice != null) {
75 cachedDevice.refreshName();
76 }
77 }
78
79 /**
80 * Search for existing {@link CachedBluetoothDevice} or return null
81 * if this device isn't in the cache. Use {@link #addDevice}
82 * to create and return a new {@link CachedBluetoothDevice} for
83 * a newly discovered {@link BluetoothDevice}.
84 *
85 * @param device the address of the Bluetooth device
86 * @return the cached device object for this device, or null if it has
87 * not been previously seen
88 */
Isha Bobrad467da72018-04-05 13:27:47 -070089 public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) {
Jason Monk7ce96b92015-02-02 11:27:58 -050090 for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
91 if (cachedDevice.getDevice().equals(device)) {
92 return cachedDevice;
93 }
94 }
Isha Bobrac3d94132018-02-08 16:04:36 -080095 for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) {
96 if (notCachedDevice.getDevice().equals(device)) {
97 return notCachedDevice;
98 }
99 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500100 return null;
101 }
102
103 /**
104 * Create and return a new {@link CachedBluetoothDevice}. This assumes
105 * that {@link #findDevice} has already been called and returned null.
106 * @param device the address of the new Bluetooth device
107 * @return the newly created CachedBluetoothDevice object
108 */
109 public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
110 LocalBluetoothProfileManager profileManager,
111 BluetoothDevice device) {
112 CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
113 profileManager, device);
Isha Bobrac3d94132018-02-08 16:04:36 -0800114 if (profileManager.getHearingAidProfile() != null
115 && profileManager.getHearingAidProfile().getHiSyncId(newDevice.getDevice())
116 != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
117 newDevice.setHiSyncId(profileManager.getHearingAidProfile()
118 .getHiSyncId(newDevice.getDevice()));
Jason Monk7ce96b92015-02-02 11:27:58 -0500119 }
Isha Bobrac3d94132018-02-08 16:04:36 -0800120 // Just add one of the hearing aids from a pair in the list that is shown in the UI.
121 if (isPairAddedInCache(newDevice.getHiSyncId())) {
122 synchronized (this) {
123 mHearingAidDevicesNotAddedInCache.add(newDevice);
124 }
125 } else {
126 synchronized (this) {
127 mCachedDevices.add(newDevice);
128 if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
129 && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) {
130 mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice);
131 }
132 mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
133 }
134 }
135
Jason Monk7ce96b92015-02-02 11:27:58 -0500136 return newDevice;
137 }
138
139 /**
Isha Bobrac3d94132018-02-08 16:04:36 -0800140 * Returns true if the one of the two hearing aid devices is already cached for UI.
141 *
142 * @param long hiSyncId
143 * @return {@code True} if one of the two hearing aid devices is is already cached for UI.
144 */
145 private synchronized boolean isPairAddedInCache(long hiSyncId) {
146 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
147 return false;
148 }
149 if(mCachedDevicesMapForHearingAids.containsKey(hiSyncId)) {
150 return true;
151 }
152 return false;
153 }
154
155 /**
156 * Returns device summary of the pair of the hearing aid passed as the parameter.
157 *
158 * @param CachedBluetoothDevice device
159 * @return Device summary, or if the pair does not exist or if its not a hearing aid,
160 * then {@code null}.
161 */
162 public synchronized String getHearingAidPairDeviceSummary(CachedBluetoothDevice device) {
163 String pairDeviceSummary = null;
164 if (device.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
165 for (CachedBluetoothDevice hearingAidDevice : mHearingAidDevicesNotAddedInCache) {
166 if (hearingAidDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
167 && hearingAidDevice.getHiSyncId() == device.getHiSyncId()) {
168 pairDeviceSummary = hearingAidDevice.getConnectionSummary();
169 }
170 }
171 }
172 return pairDeviceSummary;
173 }
174
175 /**
176 * Adds the 2nd hearing aid in a pair in a list that maintains the hearing aids that are
177 * not dispalyed in the UI.
178 *
179 * @param CachedBluetoothDevice device
180 */
181 public synchronized void addDeviceNotaddedInMap(CachedBluetoothDevice device) {
182 mHearingAidDevicesNotAddedInCache.add(device);
183 }
184
185 /**
186 * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
187 * Hearing Aid Service is connected and the HiSyncId's are now available.
188 * @param LocalBluetoothProfileManager profileManager
189 */
190 public synchronized void updateHearingAidsDevices(LocalBluetoothProfileManager profileManager) {
191 HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
192 if (profileProxy == null) {
193 log("updateHearingAidsDevices: getHearingAidProfile() is null");
194 return;
195 }
Stanley Tng16a56902018-04-20 11:54:36 -0700196 final Set<Long> syncIdChangedSet = new HashSet<Long>();
Isha Bobrac3d94132018-02-08 16:04:36 -0800197 for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
198 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
199 continue;
200 }
201
202 long newHiSyncId = profileProxy.getHiSyncId(cachedDevice.getDevice());
203
204 if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
205 cachedDevice.setHiSyncId(newHiSyncId);
Stanley Tng16a56902018-04-20 11:54:36 -0700206 syncIdChangedSet.add(newHiSyncId);
Isha Bobrac3d94132018-02-08 16:04:36 -0800207 }
208 }
Stanley Tng16a56902018-04-20 11:54:36 -0700209 for (Long syncId : syncIdChangedSet) {
210 onHiSyncIdChanged(syncId);
211 }
Isha Bobrac3d94132018-02-08 16:04:36 -0800212 }
213
214 /**
Jason Monk7ce96b92015-02-02 11:27:58 -0500215 * Attempts to get the name of a remote device, otherwise returns the address.
216 *
217 * @param device The remote device.
218 * @return The name, or if unavailable, the address.
219 */
220 public String getName(BluetoothDevice device) {
221 CachedBluetoothDevice cachedDevice = findDevice(device);
Ajay Panickerc2c4efd2017-04-03 12:15:54 -0700222 if (cachedDevice != null && cachedDevice.getName() != null) {
Jason Monk7ce96b92015-02-02 11:27:58 -0500223 return cachedDevice.getName();
224 }
225
226 String name = device.getAliasName();
227 if (name != null) {
228 return name;
229 }
230
231 return device.getAddress();
232 }
233
234 public synchronized void clearNonBondedDevices() {
Isha Bobrac3d94132018-02-08 16:04:36 -0800235
236 mCachedDevicesMapForHearingAids.entrySet().removeIf(entries
237 -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE);
238
239 mCachedDevices.removeIf(cachedDevice
240 -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
241
242 mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice
243 -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE);
Jason Monk7ce96b92015-02-02 11:27:58 -0500244 }
245
246 public synchronized void onScanningStateChanged(boolean started) {
247 if (!started) return;
Jason Monk7ce96b92015-02-02 11:27:58 -0500248 // If starting a new scan, clear old visibility
249 // Iterate in reverse order since devices may be removed.
250 for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
251 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
Jack He51520472017-07-24 12:30:08 -0700252 cachedDevice.setJustDiscovered(false);
Jason Monk7ce96b92015-02-02 11:27:58 -0500253 }
Isha Bobrac3d94132018-02-08 16:04:36 -0800254 for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
255 CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
256 notCachedDevice.setJustDiscovered(false);
257 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500258 }
259
260 public synchronized void onBtClassChanged(BluetoothDevice device) {
261 CachedBluetoothDevice cachedDevice = findDevice(device);
262 if (cachedDevice != null) {
263 cachedDevice.refreshBtClass();
264 }
265 }
266
267 public synchronized void onUuidChanged(BluetoothDevice device) {
268 CachedBluetoothDevice cachedDevice = findDevice(device);
269 if (cachedDevice != null) {
270 cachedDevice.onUuidChanged();
271 }
272 }
273
274 public synchronized void onBluetoothStateChanged(int bluetoothState) {
275 // When Bluetooth is turning off, we need to clear the non-bonded devices
276 // Otherwise, they end up showing up on the next BT enable
277 if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
278 for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
279 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
280 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
Jack He51520472017-07-24 12:30:08 -0700281 cachedDevice.setJustDiscovered(false);
Jason Monk7ce96b92015-02-02 11:27:58 -0500282 mCachedDevices.remove(i);
Isha Bobrac3d94132018-02-08 16:04:36 -0800283 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
284 && mCachedDevicesMapForHearingAids.containsKey(cachedDevice.getHiSyncId()))
285 {
286 mCachedDevicesMapForHearingAids.remove(cachedDevice.getHiSyncId());
287 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500288 } else {
289 // For bonded devices, we need to clear the connection status so that
290 // when BT is enabled next time, device connection status shall be retrieved
291 // by making a binder call.
292 cachedDevice.clearProfileConnectionState();
293 }
294 }
Isha Bobrac3d94132018-02-08 16:04:36 -0800295 for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
296 CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
297 if (notCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
298 notCachedDevice.setJustDiscovered(false);
299 mHearingAidDevicesNotAddedInCache.remove(i);
300 } else {
301 // For bonded devices, we need to clear the connection status so that
302 // when BT is enabled next time, device connection status shall be retrieved
303 // by making a binder call.
304 notCachedDevice.clearProfileConnectionState();
305 }
306 }
Jason Monk7ce96b92015-02-02 11:27:58 -0500307 }
308 }
Pavlin Radoslavovc285d552018-02-06 16:14:00 -0800309
310 public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice,
311 int bluetoothProfile) {
312 for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
313 boolean isActive = Objects.equals(cachedDevice, activeDevice);
314 cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
315 }
316 }
317
Isha Bobrac3d94132018-02-08 16:04:36 -0800318 public synchronized void onHiSyncIdChanged(long hiSyncId) {
319 int firstMatchedIndex = -1;
320
321 for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
322 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
323 if (cachedDevice.getHiSyncId() == hiSyncId) {
324 if (firstMatchedIndex != -1) {
325 /* Found the second one */
326 mCachedDevices.remove(i);
327 mHearingAidDevicesNotAddedInCache.add(cachedDevice);
328 // Since the hiSyncIds have been updated for a connected pair of hearing aids,
329 // we remove the entry of one the hearing aids from the UI. Unless the
330 // hiSyncId get updated, the system does not know its a hearing aid, so we add
331 // both the hearing aids as separate entries in the UI first, then remove one
332 // of them after the hiSyncId is populated.
333 log("onHiSyncIdChanged: removed device=" + cachedDevice + ", with hiSyncId="
334 + hiSyncId);
335 mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
336 break;
337 } else {
338 mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice);
339 firstMatchedIndex = i;
340 }
341 }
342 }
343 }
344
345 public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
346 final long hiSyncId = device.getHiSyncId();
347
348 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) return;
349
350 for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) {
351 CachedBluetoothDevice cachedDevice = mHearingAidDevicesNotAddedInCache.get(i);
352 if (cachedDevice.getHiSyncId() == hiSyncId) {
353 // TODO: Look for more cleanups on unpairing the device.
354 mHearingAidDevicesNotAddedInCache.remove(i);
355 if (device == cachedDevice) continue;
356 cachedDevice.unpair();
357 }
358 }
359 }
360
timhypengf0509322018-03-29 14:23:21 +0800361 public synchronized void dispatchAudioModeChanged() {
362 for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
363 cachedDevice.onAudioModeChanged();
364 }
365 }
366
Jason Monk7ce96b92015-02-02 11:27:58 -0500367 private void log(String msg) {
368 if (DEBUG) {
369 Log.d(TAG, msg);
370 }
371 }
372}