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