• 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.bluetooth.le.ScanFilter;
24 import android.content.Context;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.settingslib.flags.Flags;
31 
32 import java.sql.Timestamp;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 
39 /**
40  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
41  */
42 public class CachedBluetoothDeviceManager {
43     private static final String TAG = "CachedBluetoothDeviceManager";
44     private static final boolean DEBUG = BluetoothUtils.D;
45 
46     @VisibleForTesting static int sLateBondingTimeoutMillis = 10000; // 10s
47 
48     private Context mContext;
49     private final LocalBluetoothManager mBtManager;
50 
51     @VisibleForTesting
52     final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>();
53     @VisibleForTesting
54     HearingAidDeviceManager mHearingAidDeviceManager;
55     @VisibleForTesting
56     CsipDeviceManager mCsipDeviceManager;
57     BluetoothDevice mOngoingSetMemberPair;
58     boolean mIsLateBonding;
59     int mGroupIdOfLateBonding;
60 
CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager)61     public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
62         mContext = context;
63         mBtManager = localBtManager;
64         mHearingAidDeviceManager = new HearingAidDeviceManager(context, localBtManager,
65                 mCachedDevices);
66         mCsipDeviceManager = new CsipDeviceManager(context, localBtManager, mCachedDevices);
67     }
68 
getCachedDevicesCopy()69     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
70         return new ArrayList<>(mCachedDevices);
71     }
72 
onDeviceDisappeared(CachedBluetoothDevice cachedDevice)73     public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
74         cachedDevice.setJustDiscovered(false);
75         return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
76     }
77 
onDeviceNameUpdated(BluetoothDevice device)78     public void onDeviceNameUpdated(BluetoothDevice device) {
79         CachedBluetoothDevice cachedDevice = findDevice(device);
80         if (cachedDevice != null) {
81             cachedDevice.refreshName();
82         }
83     }
84 
85     /**
86      * Search for existing {@link CachedBluetoothDevice} or return null
87      * if this device isn't in the cache. Use {@link #addDevice}
88      * to create and return a new {@link CachedBluetoothDevice} for
89      * a newly discovered {@link BluetoothDevice}.
90      *
91      * @param device the address of the Bluetooth device
92      * @return the cached device object for this device, or null if it has
93      *   not been previously seen
94      */
findDevice(BluetoothDevice device)95     public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) {
96         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
97             if (cachedDevice.getDevice().equals(device)) {
98                 return cachedDevice;
99             }
100             // Check the member devices for the coordinated set if it exists
101             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
102             if (!memberDevices.isEmpty()) {
103                 for (CachedBluetoothDevice memberDevice : memberDevices) {
104                     if (memberDevice.getDevice().equals(device)) {
105                         return memberDevice;
106                     }
107                 }
108             }
109             // Check sub devices for hearing aid if it exists
110             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
111             if (subDevice != null && subDevice.getDevice().equals(device)) {
112                 return subDevice;
113             }
114         }
115 
116         return null;
117     }
118 
119     /**
120      * Create and return a new {@link CachedBluetoothDevice}. This assumes
121      * that {@link #findDevice} has already been called and returned null.
122      * @param device the new Bluetooth device
123      * @return the newly created CachedBluetoothDevice object
124      */
addDevice(BluetoothDevice device)125     public CachedBluetoothDevice addDevice(BluetoothDevice device) {
126         return addDevice(device, /*leScanFilters=*/null);
127     }
128 
129     /**
130      * Create and return a new {@link CachedBluetoothDevice}. This assumes
131      * that {@link #findDevice} has already been called and returned null.
132      * @param device the new Bluetooth device
133      * @param leScanFilters the BLE scan filters which the device matched
134      * @return the newly created CachedBluetoothDevice object
135      */
addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters)136     public CachedBluetoothDevice addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters) {
137         CachedBluetoothDevice newDevice;
138         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
139         synchronized (this) {
140             newDevice = findDevice(device);
141             if (newDevice == null) {
142                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
143                 mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
144                 mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice, leScanFilters);
145                 if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
146                         && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
147                     mCachedDevices.add(newDevice);
148                     mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
149                 }
150             }
151         }
152 
153         return newDevice;
154     }
155 
156     /**
157      * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter.
158      *
159      * @param device the remote device
160      * @return Device summary, or if the pair does not exist or if it is not a hearing aid or
161      * a CSIP set member, then {@code null}.
162      */
getSubDeviceSummary(CachedBluetoothDevice device)163     public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
164         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
165         // TODO: check the CSIP group size instead of the real member device set size, and adjust
166         // the size restriction.
167         if (!memberDevices.isEmpty()) {
168             for (CachedBluetoothDevice memberDevice : memberDevices) {
169                 if (memberDevice.isConnected()) {
170                     return memberDevice.getConnectionSummary();
171                 }
172             }
173         }
174         CachedBluetoothDevice subDevice = device.getSubDevice();
175         if (subDevice != null && subDevice.isConnected()) {
176             return subDevice.getConnectionSummary();
177         }
178         return null;
179     }
180 
181     /**
182      * Sync device status of the pair of the hearing aid if needed.
183      *
184      * @param device the remote device
185      */
syncDeviceWithinHearingAidSetIfNeeded(CachedBluetoothDevice device, int state, int profileId)186     public synchronized void syncDeviceWithinHearingAidSetIfNeeded(CachedBluetoothDevice device,
187             int state, int profileId) {
188         if (profileId == BluetoothProfile.HAP_CLIENT
189                 || profileId == BluetoothProfile.HEARING_AID
190                 || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
191             if (state == BluetoothProfile.STATE_CONNECTED) {
192                 mHearingAidDeviceManager.syncDeviceIfNeeded(device);
193             }
194         }
195     }
196 
197     /**
198      * Notifies the connection status if device is hearing device.
199      *
200      * @param device The {@link CachedBluetoothDevice} need to be hearing device
201      */
notifyHearingDevicesConnectionStatusChangedIfNeeded( @onNull CachedBluetoothDevice device)202     public synchronized void notifyHearingDevicesConnectionStatusChangedIfNeeded(
203             @NonNull CachedBluetoothDevice device) {
204         if (!device.isHearingDevice()) {
205             return;
206         }
207 
208         mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged();
209     }
210 
211     /**
212      * Search for existing sub device {@link CachedBluetoothDevice}.
213      *
214      * @param device the address of the Bluetooth device
215      * @return true for found sub / member device or false.
216      */
isSubDevice(BluetoothDevice device)217     public synchronized boolean isSubDevice(BluetoothDevice device) {
218         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
219             if (!cachedDevice.getDevice().equals(device)) {
220                 // Check the member devices of the coordinated set if it exists
221                 Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
222                 if (!memberDevices.isEmpty()) {
223                     for (CachedBluetoothDevice memberDevice : memberDevices) {
224                         if (memberDevice.getDevice().equals(device)) {
225                             return true;
226                         }
227                     }
228                     continue;
229                 }
230                 // Check sub devices of hearing aid if it exists
231                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
232                 if (subDevice != null && subDevice.getDevice().equals(device)) {
233                     return true;
234                 }
235             }
236         }
237         return false;
238     }
239 
240     /**
241      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
242      * Hearing Aid Service is connected and the HiSyncId's are now available.
243      */
updateHearingAidsDevices()244     public synchronized void updateHearingAidsDevices() {
245         mHearingAidDeviceManager.updateHearingAidsDevices();
246     }
247 
248     /**
249      * Updates the Csip devices; specifically the GroupId's. This routine is called when the
250      * CSIS is connected and the GroupId's are now available.
251      */
updateCsipDevices()252     public synchronized void updateCsipDevices() {
253         mCsipDeviceManager.updateCsipDevices();
254     }
255 
256     /**
257      * Attempts to get the name of a remote device, otherwise returns the address.
258      *
259      * @param device The remote device.
260      * @return The name, or if unavailable, the address.
261      */
getName(BluetoothDevice device)262     public String getName(BluetoothDevice device) {
263         if (isOngoingPairByCsip(device)) {
264             CachedBluetoothDevice firstDevice =
265                     mCsipDeviceManager.getFirstMemberDevice(mGroupIdOfLateBonding);
266             if (firstDevice != null && firstDevice.getName() != null) {
267                 return firstDevice.getName();
268             }
269         }
270 
271         CachedBluetoothDevice cachedDevice = findDevice(device);
272         if (cachedDevice != null && cachedDevice.getName() != null) {
273             return cachedDevice.getName();
274         }
275 
276         String name = device.getAlias();
277         if (name != null) {
278             return name;
279         }
280 
281         return device.getAddress();
282     }
283 
clearNonBondedDevices()284     public synchronized void clearNonBondedDevices() {
285         clearNonBondedSubDevices();
286         final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
287         mCachedDevices.stream()
288                 .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
289                 .forEach(cachedDevice -> {
290                     cachedDevice.release();
291                     removedCachedDevice.add(cachedDevice);
292                 });
293         mCachedDevices.removeAll(removedCachedDevice);
294     }
295 
clearNonBondedSubDevices()296     private void clearNonBondedSubDevices() {
297         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
298             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
299             Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
300             if (!memberDevices.isEmpty()) {
301                 for (Object it : memberDevices.toArray()) {
302                     CachedBluetoothDevice memberDevice = (CachedBluetoothDevice) it;
303                     // Member device exists and it is not bonded
304                     if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
305                         cachedDevice.removeMemberDevice(memberDevice);
306                     }
307                 }
308                 return;
309             }
310             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
311             if (subDevice != null
312                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
313                 // Sub device exists and it is not bonded
314                 subDevice.release();
315                 cachedDevice.setSubDevice(null);
316             }
317         }
318     }
319 
onScanningStateChanged(boolean started)320     public synchronized void onScanningStateChanged(boolean started) {
321         if (!started) return;
322         // If starting a new scan, clear old visibility
323         // Iterate in reverse order since devices may be removed.
324         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
325             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
326             cachedDevice.setJustDiscovered(false);
327             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
328             if (!memberDevices.isEmpty()) {
329                 for (CachedBluetoothDevice memberDevice : memberDevices) {
330                     memberDevice.setJustDiscovered(false);
331                 }
332                 return;
333             }
334             final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
335             if (subDevice != null) {
336                 subDevice.setJustDiscovered(false);
337             }
338         }
339     }
340 
onBluetoothStateChanged(int bluetoothState)341     public synchronized void onBluetoothStateChanged(int bluetoothState) {
342         // When Bluetooth is turning off, we need to clear the non-bonded devices
343         // Otherwise, they end up showing up on the next BT enable
344         if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
345             for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
346                 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
347                 final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
348                 if (!memberDevices.isEmpty()) {
349                     for (CachedBluetoothDevice memberDevice : memberDevices) {
350                         if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
351                             cachedDevice.removeMemberDevice(memberDevice);
352                         }
353                     }
354                 } else {
355                     CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
356                     if (subDevice != null) {
357                         if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
358                             cachedDevice.setSubDevice(null);
359                         }
360                     }
361                 }
362                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
363                     cachedDevice.setJustDiscovered(false);
364                     cachedDevice.release();
365                     mCachedDevices.remove(i);
366                 }
367             }
368 
369             // To clear the SetMemberPair flag when the Bluetooth is turning off.
370             mOngoingSetMemberPair = null;
371             mIsLateBonding = false;
372             mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
373         }
374     }
375 
removeDuplicateInstanceForIdentityAddress(BluetoothDevice device)376     synchronized void removeDuplicateInstanceForIdentityAddress(BluetoothDevice device) {
377         String identityAddress = device.getIdentityAddress();
378         if (identityAddress == null || identityAddress.equals(device.getAddress())) {
379             return;
380         }
381         mCachedDevices.removeIf(d -> {
382             boolean shouldRemove = d.getDevice().getAddress().equals(identityAddress);
383             if (shouldRemove) {
384                 Log.d(TAG, "Remove instance for identity address " + d);
385             }
386             return shouldRemove;
387         });
388     }
389 
onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state, int profileId)390     public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
391             cachedDevice, int state, int profileId) {
392         if (profileId == BluetoothProfile.HEARING_AID) {
393             return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
394                 state);
395         }
396         if (profileId == BluetoothProfile.HEADSET
397                 || profileId == BluetoothProfile.A2DP
398                 || profileId == BluetoothProfile.LE_AUDIO
399                 || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
400             return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
401                 state);
402         }
403         return false;
404     }
405 
406     /** Handles when the device been set as active/inactive. */
onActiveDeviceChanged(CachedBluetoothDevice cachedBluetoothDevice)407     public synchronized void onActiveDeviceChanged(CachedBluetoothDevice cachedBluetoothDevice) {
408         if (cachedBluetoothDevice == null) {
409             return;
410         }
411         if (cachedBluetoothDevice.isHearingDevice()) {
412             mHearingAidDeviceManager.onActiveDeviceChanged(cachedBluetoothDevice);
413             if (Flags.hearingDeviceSetConnectionStatusReport()) {
414                 mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged();
415             }
416         }
417     }
418 
onDeviceUnpaired(CachedBluetoothDevice device)419     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
420         mHearingAidDeviceManager.clearLocalDataIfNeeded(device);
421         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
422         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
423         // Should iterate through the cloned set to avoid ConcurrentModificationException
424         final Set<CachedBluetoothDevice> memberDevices = new HashSet<>(device.getMemberDevice());
425         if (!memberDevices.isEmpty()) {
426             // Main device is unpaired, also unpair the member devices
427             for (CachedBluetoothDevice memberDevice : memberDevices) {
428                 memberDevice.unpair();
429                 memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
430                 device.removeMemberDevice(memberDevice);
431             }
432         } else if (mainDevice != null) {
433             // Member device is unpaired, also unpair the main device
434             mainDevice.unpair();
435         }
436         mainDevice = mHearingAidDeviceManager.findMainDevice(device);
437         CachedBluetoothDevice subDevice = device.getSubDevice();
438         if (subDevice != null) {
439             // Main device is unpaired, to unpair sub device
440             subDevice.unpair();
441             device.setSubDevice(null);
442         } else if (mainDevice != null) {
443             // Sub device unpaired, to unpair main device
444             mainDevice.unpair();
445             mainDevice.setSubDevice(null);
446         }
447 
448         // TODO: b/386121967 - Should change to use isHearingDevice but mProfile get clear here.
449         //  Need to consider where to put this logic when using isHearingDevice()
450         if (device.isHearingAidDevice()) {
451             if (Flags.hearingDeviceSetConnectionStatusReport()) {
452                 mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged();
453             }
454         }
455     }
456 
457     /**
458      * Called when we found a set member of a group. The function will check the {@code groupId} if
459      * it exists and the bond state of the device is BOND_NOE, and if there isn't any ongoing pair
460      * , and then return {@code true} to pair the device automatically.
461      *
462      * @param device The found device
463      * @param groupId The group id of the found device
464      *
465      * @return {@code true}, if the device should pair automatically; Otherwise, return
466      * {@code false}.
467      */
shouldPairByCsip(BluetoothDevice device, int groupId)468     private synchronized boolean shouldPairByCsip(BluetoothDevice device, int groupId) {
469         boolean isOngoingSetMemberPair = mOngoingSetMemberPair != null;
470         int bondState = device.getBondState();
471         boolean groupExists = mCsipDeviceManager.isExistedGroupId(groupId);
472         Log.d(TAG,
473                 "isOngoingSetMemberPair=" + isOngoingSetMemberPair + ", bondState=" + bondState
474                         + ", groupExists=" + groupExists + ", groupId=" + groupId);
475 
476         if (isOngoingSetMemberPair || bondState != BluetoothDevice.BOND_NONE || !groupExists) {
477             return false;
478         }
479         return true;
480     }
481 
checkLateBonding(int groupId)482     private synchronized boolean checkLateBonding(int groupId) {
483         CachedBluetoothDevice firstDevice = mCsipDeviceManager.getFirstMemberDevice(groupId);
484         if (firstDevice == null) {
485             Log.d(TAG, "No first device in group: " + groupId);
486             return false;
487         }
488 
489         Timestamp then = firstDevice.getBondTimestamp();
490         if (then == null) {
491             Log.d(TAG, "No bond timestamp");
492             return true;
493         }
494 
495         Timestamp now = new Timestamp(System.currentTimeMillis());
496 
497         long diff = (now.getTime() - then.getTime());
498         Log.d(TAG, "Time difference to first bonding: " + diff + "ms");
499 
500         return diff > sLateBondingTimeoutMillis;
501     }
502 
503     /**
504      * Called to check if there is an ongoing bonding for the device and it is late bonding.
505      * If the device is not matching the ongoing bonding device then false will be returned.
506      *
507      * @param device The device to check.
508      */
isLateBonding(BluetoothDevice device)509     public synchronized boolean isLateBonding(BluetoothDevice device) {
510         if (!isOngoingPairByCsip(device)) {
511             Log.d(TAG, "isLateBonding: pair not ongoing or not matching device");
512             return false;
513         }
514 
515         Log.d(TAG, "isLateBonding: " + mIsLateBonding);
516         return mIsLateBonding;
517     }
518 
519     /**
520      * Called when we found a set member of a group. The function will check the {@code groupId} if
521      * it exists and the bond state of the device is BOND_NONE, and if there isn't any ongoing pair
522      * , and then pair the device automatically.
523      *
524      * @param device The found device
525      * @param groupId The group id of the found device
526      */
pairDeviceByCsip(BluetoothDevice device, int groupId)527     public synchronized void pairDeviceByCsip(BluetoothDevice device, int groupId) {
528         if (!shouldPairByCsip(device, groupId)) {
529             return;
530         }
531         Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " groupId=" + groupId + " by CSIP ");
532         mOngoingSetMemberPair = device;
533         mIsLateBonding = checkLateBonding(groupId);
534         mGroupIdOfLateBonding = groupId;
535         syncConfigFromMainDevice(device, groupId);
536         if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
537             Log.d(TAG, "Bonding could not be started");
538             mOngoingSetMemberPair = null;
539             mIsLateBonding = false;
540             mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
541         }
542     }
543 
syncConfigFromMainDevice(BluetoothDevice device, int groupId)544     private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
545         if (!isOngoingPairByCsip(device)) {
546             return;
547         }
548         CachedBluetoothDevice memberDevice = findDevice(device);
549         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(memberDevice);
550         if (mainDevice == null) {
551             mainDevice = mCsipDeviceManager.getCachedDevice(groupId);
552         }
553 
554         if (mainDevice == null || mainDevice.equals(memberDevice)) {
555             Log.d(TAG, "no mainDevice");
556             return;
557         }
558 
559         // The memberDevice set PhonebookAccessPermission
560         device.setPhonebookAccessPermission(mainDevice.getDevice().getPhonebookAccessPermission());
561     }
562 
563     /**
564      * Called when the bond state change. If the bond state change is related with the
565      * ongoing set member pair, the cachedBluetoothDevice will be created but the UI
566      * would not be updated. For the other case, return {@code false} to go through the normal
567      * flow.
568      *
569      * @param device The device
570      * @param bondState The new bond state
571      *
572      * @return {@code true}, if the bond state change for the device is handled inside this
573      * function, and would not like to update the UI. If not, return {@code false}.
574      */
onBondStateChangedIfProcess(BluetoothDevice device, int bondState)575     public synchronized boolean onBondStateChangedIfProcess(BluetoothDevice device, int bondState) {
576         if (!isOngoingPairByCsip(device)) {
577             return false;
578         }
579 
580         if (bondState == BluetoothDevice.BOND_BONDING) {
581             return true;
582         }
583 
584         mOngoingSetMemberPair = null;
585         mIsLateBonding = false;
586         mGroupIdOfLateBonding = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
587         if (bondState != BluetoothDevice.BOND_NONE) {
588             if (findDevice(device) == null) {
589                 final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
590                 CachedBluetoothDevice newDevice =
591                         new CachedBluetoothDevice(mContext, profileManager, device);
592                 mCachedDevices.add(newDevice);
593                 findDevice(device).connect();
594             }
595         }
596 
597         return true;
598     }
599 
600     /**
601      * Check if the device is the one which is initial paired locally by CSIP. The setting
602      * would depned on it to accept the pairing request automatically
603      *
604      * @param device The device
605      *
606      * @return {@code true}, if the device is ongoing pair by CSIP. Otherwise, return
607      * {@code false}.
608      */
isOngoingPairByCsip(BluetoothDevice device)609     public boolean isOngoingPairByCsip(BluetoothDevice device) {
610         return mOngoingSetMemberPair != null && mOngoingSetMemberPair.equals(device);
611     }
612 
613     @NonNull
getHearingAidDeviceManager()614     public HearingAidDeviceManager getHearingAidDeviceManager() {
615         return mHearingAidDeviceManager;
616     }
617 
log(String msg)618     private void log(String msg) {
619         if (DEBUG) {
620             Log.d(TAG, msg);
621         }
622     }
623 }
624