• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.BluetoothCsipSetCoordinator;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothProfile;
23 import android.content.Context;
24 import android.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 import java.util.Set;
32 
33 /**
34  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
35  */
36 public class CachedBluetoothDeviceManager {
37     private static final String TAG = "CachedBluetoothDeviceManager";
38     private static final boolean DEBUG = BluetoothUtils.D;
39 
40     private Context mContext;
41     private final LocalBluetoothManager mBtManager;
42 
43     @VisibleForTesting
44     final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
45     @VisibleForTesting
46     HearingAidDeviceManager mHearingAidDeviceManager;
47     @VisibleForTesting
48     CsipDeviceManager mCsipDeviceManager;
49     BluetoothDevice mOngoingSetMemberPair;
50 
CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager)51     public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
52         mContext = context;
53         mBtManager = localBtManager;
54         mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
55         mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
56     }
57 
getCachedDevicesCopy()58     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
59         return new ArrayList<>(mCachedDevices);
60     }
61 
onDeviceDisappeared(CachedBluetoothDevice cachedDevice)62     public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
63         cachedDevice.setJustDiscovered(false);
64         return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
65     }
66 
onDeviceNameUpdated(BluetoothDevice device)67     public void onDeviceNameUpdated(BluetoothDevice device) {
68         CachedBluetoothDevice cachedDevice = findDevice(device);
69         if (cachedDevice != null) {
70             cachedDevice.refreshName();
71         }
72     }
73 
74     /**
75      * Search for existing {@link CachedBluetoothDevice} or return null
76      * if this device isn't in the cache. Use {@link #addDevice}
77      * to create and return a new {@link CachedBluetoothDevice} for
78      * a newly discovered {@link BluetoothDevice}.
79      *
80      * @param device the address of the Bluetooth device
81      * @return the cached device object for this device, or null if it has
82      *   not been previously seen
83      */
findDevice(BluetoothDevice device)84     public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) {
85         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
86             if (cachedDevice.getDevice().equals(device)) {
87                 return cachedDevice;
88             }
89             // Check the member devices for the coordinated set if it exists
90             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
91             if (!memberDevices.isEmpty()) {
92                 for (CachedBluetoothDevice memberDevice : memberDevices) {
93                     if (memberDevice.getDevice().equals(device)) {
94                         return memberDevice;
95                     }
96                 }
97             }
98             // Check sub devices for hearing aid if it exists
99             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
100             if (subDevice != null && subDevice.getDevice().equals(device)) {
101                 return subDevice;
102             }
103         }
104 
105         return null;
106     }
107 
108     /**
109      * Create and return a new {@link CachedBluetoothDevice}. This assumes
110      * that {@link #findDevice} has already been called and returned null.
111      * @param device the address of the new Bluetooth device
112      * @return the newly created CachedBluetoothDevice object
113      */
addDevice(BluetoothDevice device)114     public CachedBluetoothDevice addDevice(BluetoothDevice device) {
115         CachedBluetoothDevice newDevice;
116         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
117         synchronized (this) {
118             newDevice = findDevice(device);
119             if (newDevice == null) {
120                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
121                 mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
122                 mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
123                 if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
124                         && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
125                     mCachedDevices.add(newDevice);
126                     mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
127                 }
128             }
129         }
130 
131         return newDevice;
132     }
133 
134     /**
135      * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter.
136      *
137      * @param CachedBluetoothDevice device
138      * @return Device summary, or if the pair does not exist or if it is not a hearing aid or
139      * a CSIP set member, then {@code null}.
140      */
getSubDeviceSummary(CachedBluetoothDevice device)141     public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
142         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
143         // TODO: check the CSIP group size instead of the real member device set size, and adjust
144         // the size restriction.
145         if (!memberDevices.isEmpty()) {
146             for (CachedBluetoothDevice memberDevice : memberDevices) {
147                 if (memberDevice.isConnected()) {
148                     return memberDevice.getConnectionSummary();
149                 }
150             }
151         }
152         CachedBluetoothDevice subDevice = device.getSubDevice();
153         if (subDevice != null && subDevice.isConnected()) {
154             return subDevice.getConnectionSummary();
155         }
156         return null;
157     }
158 
159     /**
160      * Search for existing sub device {@link CachedBluetoothDevice}.
161      *
162      * @param device the address of the Bluetooth device
163      * @return true for found sub / member device or false.
164      */
isSubDevice(BluetoothDevice device)165     public synchronized boolean isSubDevice(BluetoothDevice device) {
166         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
167             if (!cachedDevice.getDevice().equals(device)) {
168                 // Check the member devices of the coordinated set if it exists
169                 Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
170                 if (!memberDevices.isEmpty()) {
171                     for (CachedBluetoothDevice memberDevice : memberDevices) {
172                         if (memberDevice.getDevice().equals(device)) {
173                             return true;
174                         }
175                     }
176                     continue;
177                 }
178                 // Check sub devices of hearing aid if it exists
179                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
180                 if (subDevice != null && subDevice.getDevice().equals(device)) {
181                     return true;
182                 }
183             }
184         }
185         return false;
186     }
187 
188     /**
189      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
190      * Hearing Aid Service is connected and the HiSyncId's are now available.
191      * @param LocalBluetoothProfileManager profileManager
192      */
updateHearingAidsDevices()193     public synchronized void updateHearingAidsDevices() {
194         mHearingAidDeviceManager.updateHearingAidsDevices();
195     }
196 
197     /**
198      * Updates the Csip devices; specifically the GroupId's. This routine is called when the
199      * CSIS is connected and the GroupId's are now available.
200      */
updateCsipDevices()201     public synchronized void updateCsipDevices() {
202         mCsipDeviceManager.updateCsipDevices();
203     }
204 
205     /**
206      * Attempts to get the name of a remote device, otherwise returns the address.
207      *
208      * @param device The remote device.
209      * @return The name, or if unavailable, the address.
210      */
getName(BluetoothDevice device)211     public String getName(BluetoothDevice device) {
212         CachedBluetoothDevice cachedDevice = findDevice(device);
213         if (cachedDevice != null && cachedDevice.getName() != null) {
214             return cachedDevice.getName();
215         }
216 
217         String name = device.getAlias();
218         if (name != null) {
219             return name;
220         }
221 
222         return device.getAddress();
223     }
224 
clearNonBondedDevices()225     public synchronized void clearNonBondedDevices() {
226         clearNonBondedSubDevices();
227         mCachedDevices.removeIf(cachedDevice
228             -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
229     }
230 
clearNonBondedSubDevices()231     private void clearNonBondedSubDevices() {
232         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
233             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
234             Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
235             if (!memberDevices.isEmpty()) {
236                 for (Object it : memberDevices.toArray()) {
237                     CachedBluetoothDevice memberDevice = (CachedBluetoothDevice) it;
238                     // Member device exists and it is not bonded
239                     if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
240                         cachedDevice.removeMemberDevice(memberDevice);
241                     }
242                 }
243                 return;
244             }
245             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
246             if (subDevice != null
247                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
248                 // Sub device exists and it is not bonded
249                 cachedDevice.setSubDevice(null);
250             }
251         }
252     }
253 
onScanningStateChanged(boolean started)254     public synchronized void onScanningStateChanged(boolean started) {
255         if (!started) return;
256         // If starting a new scan, clear old visibility
257         // Iterate in reverse order since devices may be removed.
258         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
259             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
260             cachedDevice.setJustDiscovered(false);
261             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
262             if (!memberDevices.isEmpty()) {
263                 for (CachedBluetoothDevice memberDevice : memberDevices) {
264                     memberDevice.setJustDiscovered(false);
265                 }
266                 return;
267             }
268             final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
269             if (subDevice != null) {
270                 subDevice.setJustDiscovered(false);
271             }
272         }
273     }
274 
onBluetoothStateChanged(int bluetoothState)275     public synchronized void onBluetoothStateChanged(int bluetoothState) {
276         // When Bluetooth is turning off, we need to clear the non-bonded devices
277         // Otherwise, they end up showing up on the next BT enable
278         if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
279             for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
280                 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
281                 final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
282                 if (!memberDevices.isEmpty()) {
283                     for (CachedBluetoothDevice memberDevice : memberDevices) {
284                         if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
285                             cachedDevice.removeMemberDevice(memberDevice);
286                         }
287                     }
288                 } else {
289                     CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
290                     if (subDevice != null) {
291                         if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
292                             cachedDevice.setSubDevice(null);
293                         }
294                     }
295                 }
296                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
297                     cachedDevice.setJustDiscovered(false);
298                     mCachedDevices.remove(i);
299                 }
300             }
301 
302             // To clear the SetMemberPair flag when the Bluetooth is turning off.
303             mOngoingSetMemberPair = null;
304         }
305     }
306 
onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state, int profileId)307     public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
308             cachedDevice, int state, int profileId) {
309         if (profileId == BluetoothProfile.HEARING_AID) {
310             return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
311                 state);
312         }
313         if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
314             return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
315                 state);
316         }
317         return false;
318     }
319 
onDeviceUnpaired(CachedBluetoothDevice device)320     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
321         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
322         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
323         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
324         if (!memberDevices.isEmpty()) {
325             // Main device is unpaired, to unpair the member device
326             for (CachedBluetoothDevice memberDevice : memberDevices) {
327                 memberDevice.unpair();
328                 memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
329                 device.removeMemberDevice(memberDevice);
330             }
331         } else if (mainDevice != null) {
332             // the member device unpaired, to unpair main device
333             mainDevice.unpair();
334         }
335         mainDevice = mHearingAidDeviceManager.findMainDevice(device);
336         CachedBluetoothDevice subDevice = device.getSubDevice();
337         if (subDevice != null) {
338             // Main device is unpaired, to unpair sub device
339             subDevice.unpair();
340             device.setSubDevice(null);
341         } else if (mainDevice != null) {
342             // Sub device unpaired, to unpair main device
343             mainDevice.unpair();
344             mainDevice.setSubDevice(null);
345         }
346     }
347 
348     /**
349      * Called when we found a set member of a group. The function will check the {@code groupId} if
350      * it exists and the bond state of the device is BOND_NOE, and if there isn't any ongoing pair
351      * , and then return {@code true} to pair the device automatically.
352      *
353      * @param device The found device
354      * @param groupId The group id of the found device
355      *
356      * @return {@code true}, if the device should pair automatically; Otherwise, return
357      * {@code false}.
358      */
shouldPairByCsip(BluetoothDevice device, int groupId)359     private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
360         boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
361         int bondState = device.getBondState();
362         if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE
363                 || !mCsipDeviceManager.isExistedGroupId(groupId)) {
364             Log.d(TAG, "isOngoingSetMemberPair: " + isOngoingSetMemberPair
365                     + " , device.getBondState: " + bondState);
366             return false;
367         }
368         return true;
369     }
370 
371     /**
372      * Called when we found a set member of a group. The function will check the {@code groupId} if
373      * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
374      * , and then pair the device automatically.
375      *
376      * @param device The found device
377      * @param groupId The group id of the found device
378      */
pairDeviceByCsip(BluetoothDevice device, int groupId)379     public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
380         if (!shouldPairByCsip(device, groupId)) {
381             return;
382         }
383         Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
384         mOngoingSetMemberPair = device;
385         syncConfigFromMainDevice(device, groupId);
386         if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
387             Log.d(TAG, "Bonding could not be started");
388             mOngoingSetMemberPair = null;
389         }
390     }
391 
syncConfigFromMainDevice(BluetoothDevice device, int groupId)392     private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
393         if (!isOngoingPairByCsip(device)) {
394             return;
395         }
396         CachedBluetoothDevice memberDevice = findDevice(device);
397         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
398         if (mainDevice == null) {
399             mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
400         }
401 
402         if (mainDevice == null || mainDevice.equals(memberDevice)) {
403             Log.d(TAG, "no mainDevice");
404             return;
405         }
406 
407         // The memberDevice set PhonebookAccessPermission
408         device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
409     }
410 
411     /**
412      * Called when the bond state change. If the bond state change is related with the
413      * ongoing set member pair, the cachedBluetoothDevice will be created but the UI
414      * would not be updated. For the other case, return {@code false} to go through the normal
415      * flow.
416      *
417      * @param device The device
418      * @param bondState The new bond state
419      *
420      * @return {@code true}, if the bond state change for the device is handled inside this
421      * function, and would not like to update the UI. If not, return {@code false}.
422      */
onBondStateChangedIfProcess(BluetoothDevice device, int bondState)423     public synchronized boolean onBondStateChangedIfProcess(BluetoothDevice device, int bondState) {
424         if (mOngoingSetMemberPair == null || !mOngoingSetMemberPair.equals(device)) {
425             return false;
426         }
427 
428         if (bondState == BluetoothDevice.BOND_BONDING) {
429             return true;
430         }
431 
432         mOngoingSetMemberPair = null;
433         if (bondState != BluetoothDevice.BOND_NONE) {
434             if (findDevice(device) == null) {
435                 final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
436                 CachedBluetoothDevice newDevice =
437                         new CachedBluetoothDevice(mContext, profileManager, device);
438                 mCachedDevices.add(newDevice);
439                 findDevice(device).connect();
440             }
441         }
442 
443         return true;
444     }
445 
446     /**
447      * Check if the device is the one which is initial paired locally by CSIP. The setting
448      * would depned on it to accept the pairing request automatically
449      *
450      * @param device The device
451      *
452      * @return {@code true}, if the device is ongoing pair by CSIP. Otherwise, return
453      * {@code false}.
454      */
isOngoingPairByCsip(BluetoothDevice device)455     public boolean isOngoingPairByCsip(BluetoothDevice device) {
456         return !(mOngoingSetMemberPair == null) && mOngoingSetMemberPair.equals(device);
457     }
458 
log(String msg)459     private void log(String msg) {
460         if (DEBUG) {
461             Log.d(TAG, msg);
462         }
463     }
464 }
465