1 /* 2 * Copyright (c) 2016 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.bluetooth.pbapclient; 18 19 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 22 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.accounts.Account; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.BluetoothUuid; 30 import android.bluetooth.SdpPseRecord; 31 import android.content.ComponentName; 32 import android.os.Handler; 33 import android.os.HandlerThread; 34 import android.os.Looper; 35 import android.os.ParcelUuid; 36 import android.os.Parcelable; 37 import android.provider.CallLog; 38 import android.sysprop.BluetoothProperties; 39 import android.util.Log; 40 41 import com.android.bluetooth.btservice.AdapterService; 42 import com.android.bluetooth.btservice.ProfileService; 43 import com.android.bluetooth.btservice.storage.DatabaseManager; 44 import com.android.bluetooth.flags.Flags; 45 import com.android.bluetooth.hfpclient.HfpClientConnectionService; 46 import com.android.bluetooth.sdp.SdpManagerNativeInterface; 47 import com.android.internal.annotations.VisibleForTesting; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.concurrent.ConcurrentHashMap; 53 54 /** Provides Bluetooth Phone Book Access Profile Client profile. */ 55 public class PbapClientService extends ProfileService { 56 private static final String TAG = PbapClientService.class.getSimpleName(); 57 58 private static final String SERVICE_NAME = "Phonebook Access PCE"; 59 60 /** The component names for the owned authenticator service */ 61 private static final String AUTHENTICATOR_SERVICE = 62 PbapClientAccountAuthenticatorService.class.getCanonicalName(); 63 64 // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. 65 private static final int MAXIMUM_DEVICES = 10; 66 67 @VisibleForTesting 68 final Map<BluetoothDevice, PbapClientStateMachineOld> mPbapClientStateMachineOldMap = 69 new ConcurrentHashMap<>(); 70 71 private static PbapClientService sPbapClientService; 72 private final PbapClientContactsStorage mPbapClientContactsStorage; 73 private final PbapClientAccountManager mPbapClientAccountManager; 74 private final DatabaseManager mDatabaseManager; 75 private final Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap; 76 private final Handler mHandler; 77 78 private int mSdpHandle = -1; 79 80 class PbapClientStateMachineCallback implements PbapClientStateMachine.Callback { 81 private final BluetoothDevice mDevice; 82 PbapClientStateMachineCallback(BluetoothDevice device)83 PbapClientStateMachineCallback(BluetoothDevice device) { 84 mDevice = device; 85 } 86 87 @Override onConnectionStateChanged(int oldState, int newState)88 public void onConnectionStateChanged(int oldState, int newState) { 89 Log.v( 90 TAG, 91 "Device connection state changed, device=" 92 + mDevice 93 + ", old=" 94 + oldState 95 + ", new=" 96 + newState); 97 if (oldState != newState && newState == STATE_DISCONNECTED) { 98 removeDevice(mDevice); 99 } 100 } 101 } 102 103 class PbapClientAccountManagerCallback implements PbapClientAccountManager.Callback { 104 @Override onAccountsChanged(List<Account> oldAccounts, List<Account> newAccounts)105 public void onAccountsChanged(List<Account> oldAccounts, List<Account> newAccounts) { 106 Log.i(TAG, "onAccountsChanged: old=" + oldAccounts + ", new=" + newAccounts); 107 if (oldAccounts == null) { 108 removeUncleanAccounts(); 109 for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) { 110 smOld.tryDownloadIfConnected(); 111 } 112 } 113 } 114 } 115 PbapClientService(AdapterService adapterService)116 public PbapClientService(AdapterService adapterService) { 117 super(requireNonNull(adapterService)); 118 mDatabaseManager = requireNonNull(adapterService.getDatabase()); 119 mHandler = new Handler(Looper.getMainLooper()); 120 121 if (Flags.pbapClientStorageRefactor()) { 122 mPbapClientContactsStorage = new PbapClientContactsStorage(adapterService); 123 mPbapClientAccountManager = null; 124 mPbapClientStateMachineMap = new ConcurrentHashMap<>(); 125 mPbapClientContactsStorage.start(); 126 } else { 127 mPbapClientAccountManager = 128 new PbapClientAccountManager( 129 adapterService, new PbapClientAccountManagerCallback()); 130 mPbapClientContactsStorage = null; 131 mPbapClientStateMachineMap = null; 132 mPbapClientAccountManager.start(); 133 } 134 135 setComponentAvailable(AUTHENTICATOR_SERVICE, true); 136 137 registerSdpRecord(); 138 setPbapClientService(this); 139 } 140 141 @VisibleForTesting PbapClientService( AdapterService adapterService, PbapClientContactsStorage storage, Map<BluetoothDevice, PbapClientStateMachine> deviceMap)142 PbapClientService( 143 AdapterService adapterService, 144 PbapClientContactsStorage storage, 145 Map<BluetoothDevice, PbapClientStateMachine> deviceMap) { 146 super(requireNonNull(adapterService)); 147 mDatabaseManager = requireNonNull(adapterService.getDatabase()); 148 mHandler = new Handler(Looper.getMainLooper()); 149 150 mPbapClientContactsStorage = storage; 151 mPbapClientStateMachineMap = deviceMap; 152 153 // For compatibility with tests while we phase the old state machine out 154 mPbapClientAccountManager = 155 new PbapClientAccountManager( 156 adapterService, new PbapClientAccountManagerCallback()); 157 158 setComponentAvailable(AUTHENTICATOR_SERVICE, true); 159 160 if (Flags.pbapClientStorageRefactor()) { 161 mPbapClientContactsStorage.start(); 162 } else { 163 mPbapClientAccountManager.start(); 164 } 165 166 registerSdpRecord(); 167 setPbapClientService(this); 168 } 169 isEnabled()170 public static boolean isEnabled() { 171 return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); 172 } 173 174 @Override initBinder()175 public IProfileServiceBinder initBinder() { 176 return new PbapClientServiceBinder(this); 177 } 178 179 @Override cleanup()180 public void cleanup() { 181 Log.i(TAG, "Cleanup PbapClient Service"); 182 183 setPbapClientService(null); 184 cleanUpSdpRecord(); 185 186 // Unregister SDP event handler and stop all queued messages. 187 mHandler.removeCallbacksAndMessages(null); 188 189 if (Flags.pbapClientStorageRefactor()) { 190 // Try to bring down all the connections gracefully 191 synchronized (mPbapClientStateMachineMap) { 192 for (PbapClientStateMachine sm : mPbapClientStateMachineMap.values()) { 193 sm.disconnect(); 194 } 195 mPbapClientStateMachineMap.clear(); 196 } 197 mPbapClientContactsStorage.stop(); 198 } else { 199 for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) { 200 smOld.doQuit(); 201 } 202 mPbapClientStateMachineOldMap.clear(); 203 removeUncleanAccounts(); 204 mPbapClientAccountManager.stop(); 205 } 206 207 setComponentAvailable(AUTHENTICATOR_SERVICE, false); 208 } 209 210 /** 211 * Add our PBAP Client SDP record to the device SDP database 212 * 213 * <p>This allows our client to be recognized by the remove device. The record must be cleaned 214 * up when we shutdown. 215 */ registerSdpRecord()216 private void registerSdpRecord() { 217 SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance(); 218 if (!nativeInterface.isAvailable()) { 219 Log.e(TAG, "SdpManagerNativeInterface is not available"); 220 return; 221 } 222 mSdpHandle = nativeInterface.createPbapPceRecord(SERVICE_NAME, PbapSdpRecord.VERSION_1_2); 223 } 224 225 /** 226 * Remove our PBAP Client SDP record from the device SDP database 227 * 228 * <p>Gracefully removes PBAP Client support from our SDP records. Called when shutting down. 229 */ cleanUpSdpRecord()230 private void cleanUpSdpRecord() { 231 if (mSdpHandle < 0) { 232 Log.e(TAG, "cleanUpSdpRecord, SDP record never created"); 233 return; 234 } 235 int sdpHandle = mSdpHandle; 236 mSdpHandle = -1; 237 SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance(); 238 if (!nativeInterface.isAvailable()) { 239 Log.e( 240 TAG, 241 "cleanUpSdpRecord failed, SdpManagerNativeInterface is not available," 242 + " sdpHandle=" 243 + sdpHandle); 244 return; 245 } 246 Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle); 247 if (!nativeInterface.removeSdpRecord(sdpHandle)) { 248 Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle); 249 } 250 } 251 getDeviceStateMachine(BluetoothDevice device)252 private PbapClientStateMachine getDeviceStateMachine(BluetoothDevice device) { 253 synchronized (mPbapClientStateMachineMap) { 254 return mPbapClientStateMachineMap.get(device); 255 } 256 } 257 258 /** 259 * Create a state machine for a device 260 * 261 * <p>PBAP Client connections are always outgoing. This function creates a device state machine 262 * instance, which will manage the connection and data lifecycles of the device. 263 */ addDevice(BluetoothDevice device)264 private boolean addDevice(BluetoothDevice device) { 265 Log.d(TAG, "add device, device=" + device); 266 synchronized (mPbapClientStateMachineMap) { 267 PbapClientStateMachine stateMachine = mPbapClientStateMachineMap.get(device); 268 if (stateMachine == null) { 269 if (mPbapClientStateMachineMap.size() >= MAXIMUM_DEVICES) { 270 Log.w(TAG, "Cannot connect " + device + ", too many devices connected already"); 271 return false; 272 } 273 stateMachine = 274 new PbapClientStateMachine( 275 device, 276 mPbapClientContactsStorage, 277 this, 278 new PbapClientStateMachineCallback(device)); 279 stateMachine.start(); 280 stateMachine.connect(); 281 mPbapClientStateMachineMap.put(device, stateMachine); 282 return true; 283 } else { 284 Log.w(TAG, "Cannot connect " + device + ", already connecting/connected."); 285 return false; 286 } 287 } 288 } 289 290 /** 291 * Remove a device state machine, if it exists 292 * 293 * <p>When a device disconnects, we gracefully clean up its state machine instance and drop our 294 * reference to it. State machines cannot be reused, so this must be deleted before a device can 295 * reconnect. 296 */ removeDevice(BluetoothDevice device)297 private void removeDevice(BluetoothDevice device) { 298 Log.d(TAG, "remove device, device=" + device); 299 synchronized (mPbapClientStateMachineMap) { 300 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 301 if (pbapClientStateMachine != null) { 302 int state = pbapClientStateMachine.getConnectionState(); 303 if (state != STATE_DISCONNECTED) { 304 Log.w(TAG, "Removing connected device, device=" + device + ", state=" + state); 305 } 306 mPbapClientStateMachineMap.remove(device); 307 } 308 } 309 } 310 311 /** 312 * Clean up any existing accounts. 313 * 314 * <p>This function gets the list of available Pbap Client accounts and deletes them. Deletion 315 * of the account causes Contacts Provider to also delete the associated contacts data. We 316 * separately clean up the call log data associated with a given account too. 317 */ removeUncleanAccounts()318 private void removeUncleanAccounts() { 319 if (Flags.pbapClientStorageRefactor()) { 320 Log.i(TAG, "removeUncleanAccounts: this is the responsibility of contacts storage"); 321 return; 322 } 323 324 List<Account> accounts = mPbapClientAccountManager.getAccounts(); 325 Log.i(TAG, "removeUncleanAccounts: Found " + accounts.size() + " accounts"); 326 327 for (Account account : accounts) { 328 Log.d(TAG, "removeUncleanAccounts: removing call logs for account=" + account); 329 try { 330 // The device ID for call logs is the name of the account 331 getContentResolver() 332 .delete( 333 CallLog.Calls.CONTENT_URI, 334 CallLog.Calls.PHONE_ACCOUNT_ID + "=?", 335 new String[] {account.name}); 336 } catch (IllegalArgumentException e) { 337 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.", e); 338 } 339 340 Log.i(TAG, "removeUncleanAccounts: removing account=" + account); 341 mPbapClientAccountManager.removeAccount(account); 342 } 343 } 344 removeHfpCallLog(String accountName)345 private void removeHfpCallLog(String accountName) { 346 Log.d(TAG, "Removing call logs from " + accountName); 347 // Delete call logs belonging to accountName==BD_ADDR that also match component "hfpclient" 348 ComponentName componentName = new ComponentName(this, HfpClientConnectionService.class); 349 String selectionFilter = 350 CallLog.Calls.PHONE_ACCOUNT_ID 351 + "=? AND " 352 + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME 353 + "=?"; 354 String[] selectionArgs = new String[] {accountName, componentName.flattenToString()}; 355 try { 356 getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); 357 } catch (IllegalArgumentException e) { 358 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 359 } 360 } 361 362 /** 363 * Ensure that after HFP disconnects, we remove call logs. This addresses the situation when 364 * PBAP was never connected while calls were made. Ideally {@link PbapClientConnectionHandler} 365 * has code to remove call logs when PBAP disconnects. 366 */ handleHeadsetClientConnectionStateChanged( BluetoothDevice device, int oldState, int newState)367 public void handleHeadsetClientConnectionStateChanged( 368 BluetoothDevice device, int oldState, int newState) { 369 if (newState == STATE_DISCONNECTED) { 370 Log.d(TAG, "Received intent to disconnect HFP with " + device); 371 if (Flags.pbapClientStorageRefactor()) { 372 Account account = mPbapClientContactsStorage.getStorageAccountForDevice(device); 373 mPbapClientContactsStorage.removeCallHistory(account); 374 return; 375 } else { 376 // HFP client stores entries in calllog.db by BD_ADDR and component name 377 // Using the current Service as the context. 378 removeHfpCallLog(device.getAddress()); 379 } 380 } 381 } 382 383 /** 384 * Get debug information about this PbapClientService instance 385 * 386 * @param sb The StringBuilder instance to add our debug dump info to 387 */ 388 @Override dump(StringBuilder sb)389 public void dump(StringBuilder sb) { 390 super.dump(sb); 391 392 if (Flags.pbapClientStorageRefactor()) { 393 synchronized (mPbapClientStateMachineMap) { 394 ProfileService.println( 395 sb, 396 "Devices (" 397 + mPbapClientStateMachineMap.size() 398 + "/ " 399 + MAXIMUM_DEVICES 400 + ")"); 401 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 402 stateMachine.dump(sb); 403 ProfileService.println(sb, ""); 404 } 405 } 406 ProfileService.println(sb, mPbapClientContactsStorage.dump()); 407 } else { 408 for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) { 409 smOld.dump(sb); 410 } 411 ProfileService.println(sb, mPbapClientAccountManager.dump()); 412 } 413 } 414 415 // ********************************************************************************************* 416 // * Events from AdapterService 417 // ********************************************************************************************* 418 419 /** 420 * Get notified of incoming ACL disconnections 421 * 422 * <p>OBEX client's are supposed to be in control of the connection lifecycle, and servers are 423 * not supposed to disconnect OBEX sessions. Despite this, its normal/possible the remote device 424 * to tear down connections at lower levels than OBEX, mainly the L2CAP/RFCOMM links or the ACL. 425 * The OBEX framework isn't setup to be notified of these disconnections, so we must listen for 426 * them separately and clean up the device connection and, if necessary, data when this happens. 427 * 428 * @param device The device that had the ACL disconnect 429 * @param transport The transport the device disconnected on 430 */ aclDisconnected(BluetoothDevice device, int transport)431 public void aclDisconnected(BluetoothDevice device, int transport) { 432 mHandler.post(() -> handleAclDisconnected(device, transport)); 433 } 434 handleAclDisconnected(BluetoothDevice device, int transport)435 private void handleAclDisconnected(BluetoothDevice device, int transport) { 436 Log.i( 437 TAG, 438 "Received ACL disconnection event, device=" 439 + device.toString() 440 + ", transport=" 441 + transport); 442 443 if (transport != BluetoothDevice.TRANSPORT_BREDR) { 444 return; 445 } 446 447 if (getConnectionState(device) == STATE_CONNECTED) { 448 disconnect(device); 449 } 450 } 451 452 /** 453 * Get notified of incoming SDP records 454 * 455 * <p>This function looks for PBAP Server records coming from remote devices, and forwards them 456 * to the appropriate device's state machine instance for processing. SDP records are used to 457 * determine which L2CAP/RFCOMM psm/channel to connect on, as well as which phonebooks to expect 458 */ receiveSdpSearchRecord( BluetoothDevice device, int status, Parcelable record, ParcelUuid uuid)459 public void receiveSdpSearchRecord( 460 BluetoothDevice device, int status, Parcelable record, ParcelUuid uuid) { 461 Log.v( 462 TAG, 463 "Received SDP record for UUID=" 464 + uuid.toString() 465 + " (expected UUID=" 466 + BluetoothUuid.PBAP_PSE.toString() 467 + ")"); 468 if (uuid.equals(BluetoothUuid.PBAP_PSE)) { 469 SdpPseRecord pseRecord = (SdpPseRecord) record; 470 if (pseRecord == null) { 471 Log.w(TAG, "Received null PSE record for device=" + device); 472 return; 473 } 474 475 if (Flags.pbapClientStorageRefactor()) { 476 PbapClientStateMachine stateMachine = getDeviceStateMachine(device); 477 if (stateMachine == null) { 478 Log.e(TAG, "No StateMachine found for the device=" + device.toString()); 479 return; 480 } 481 stateMachine.onSdpResultReceived(status, new PbapSdpRecord(device, pseRecord)); 482 } else { 483 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device); 484 if (smOld == null) { 485 Log.e(TAG, "No StateMachine found for the device=" + device.toString()); 486 return; 487 } 488 smOld.onSdpResultReceived(status, new PbapSdpRecord(device, pseRecord)); 489 } 490 } 491 } 492 493 // ********************************************************************************************* 494 // * API methods 495 // ********************************************************************************************* 496 497 /** Get the singleton instance of PbapClientService, if one exists */ getPbapClientService()498 public static synchronized PbapClientService getPbapClientService() { 499 if (sPbapClientService == null) { 500 Log.w(TAG, "getPbapClientService(): service is null"); 501 return null; 502 } 503 if (!sPbapClientService.isAvailable()) { 504 Log.w(TAG, "getPbapClientService(): service is not available"); 505 return null; 506 } 507 return sPbapClientService; 508 } 509 510 /** 511 * Set the singleton instance of PbapClientService 512 * 513 * <p>This function is meant to be used by tests only. 514 */ 515 @VisibleForTesting setPbapClientService(PbapClientService instance)516 static synchronized void setPbapClientService(PbapClientService instance) { 517 Log.v(TAG, "setPbapClientService(): set to: " + instance); 518 sPbapClientService = instance; 519 } 520 521 /** 522 * Requests a connection to the given device's PBAP Server 523 * 524 * @param device is the device with which we will connect to 525 * @return true if we successfully begin the connection process, false otherwise 526 */ connect(BluetoothDevice device)527 public boolean connect(BluetoothDevice device) { 528 if (device == null) { 529 throw new IllegalArgumentException("Null device"); 530 } 531 Log.d(TAG, "connect(device=" + device.getAddress() + ")"); 532 if (getConnectionPolicy(device) <= CONNECTION_POLICY_FORBIDDEN) { 533 return false; 534 } 535 536 if (Flags.pbapClientStorageRefactor()) { 537 return addDevice(device); 538 } else { 539 synchronized (mPbapClientStateMachineOldMap) { 540 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device); 541 if (smOld == null && mPbapClientStateMachineOldMap.size() < MAXIMUM_DEVICES) { 542 HandlerThread smThread = new HandlerThread("PbapClientStateMachineOld"); 543 smThread.start(); 544 545 smOld = new PbapClientStateMachineOld(this, device, smThread); 546 smOld.start(); 547 mPbapClientStateMachineOldMap.put(device, smOld); 548 return true; 549 } else { 550 Log.w(TAG, "Received connect request while already connecting/connected."); 551 return false; 552 } 553 } 554 } 555 } 556 557 /** 558 * Disconnects the pbap client profile from the passed in device 559 * 560 * @param device is the device with which we will disconnect the pbap client profile 561 * @return true if we disconnected the pbap client profile, false otherwise 562 */ disconnect(BluetoothDevice device)563 public boolean disconnect(BluetoothDevice device) { 564 if (device == null) { 565 throw new IllegalArgumentException("Null device"); 566 } 567 568 Log.d(TAG, "disconnect(device=" + device.getAddress() + ")"); 569 if (Flags.pbapClientStorageRefactor()) { 570 PbapClientStateMachine pbapClientStateMachine = getDeviceStateMachine(device); 571 if (pbapClientStateMachine != null) { 572 pbapClientStateMachine.disconnect(); 573 return true; 574 } 575 } else { 576 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device); 577 if (smOld != null) { 578 smOld.disconnect(device); 579 return true; 580 } 581 } 582 583 Log.w(TAG, "disconnect() called on unconnected device."); 584 return false; 585 } 586 587 /** 588 * Get the list of PBAP Server devices this PBAP Client device is connected to 589 * 590 * @return The list of connected PBAP Server devices 591 */ getConnectedDevices()592 public List<BluetoothDevice> getConnectedDevices() { 593 int[] desiredStates = {STATE_CONNECTED}; 594 return getDevicesMatchingConnectionStates(desiredStates); 595 } 596 597 /** 598 * Get the list of PBAP Server devices this PBAP Client device know about, who are in a given 599 * state. 600 * 601 * @param states The array of BluetoothProfile states you want to match on 602 * @return The list of connected PBAP Server devices 603 */ getDevicesMatchingConnectionStates(int[] states)604 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 605 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); 606 607 if (Flags.pbapClientStorageRefactor()) { 608 synchronized (mPbapClientStateMachineMap) { 609 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : 610 mPbapClientStateMachineMap.entrySet()) { 611 int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); 612 for (int state : states) { 613 if (currentDeviceState == state) { 614 deviceList.add(stateMachineEntry.getKey()); 615 break; 616 } 617 } 618 } 619 } 620 } else { 621 for (Map.Entry<BluetoothDevice, PbapClientStateMachineOld> stateMachineEntryOld : 622 mPbapClientStateMachineOldMap.entrySet()) { 623 int currentDeviceState = stateMachineEntryOld.getValue().getConnectionState(); 624 for (int state : states) { 625 if (currentDeviceState == state) { 626 deviceList.add(stateMachineEntryOld.getKey()); 627 break; 628 } 629 } 630 } 631 } 632 633 return deviceList; 634 } 635 636 /** 637 * Get the current connection state of the profile 638 * 639 * @param device is the remote bluetooth device 640 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, {@link 641 * BluetoothProfile#STATE_CONNECTING} if this profile is being connected, {@link 642 * BluetoothProfile#STATE_CONNECTED} if this profile is connected, or {@link 643 * BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 644 */ getConnectionState(BluetoothDevice device)645 public int getConnectionState(BluetoothDevice device) { 646 if (device == null) { 647 throw new IllegalArgumentException("Null device"); 648 } 649 650 if (Flags.pbapClientStorageRefactor()) { 651 PbapClientStateMachine pbapClientStateMachine = getDeviceStateMachine(device); 652 if (pbapClientStateMachine == null) { 653 return STATE_DISCONNECTED; 654 } else { 655 return pbapClientStateMachine.getConnectionState(); 656 } 657 } else { 658 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device); 659 if (smOld == null) { 660 return STATE_DISCONNECTED; 661 } else { 662 return smOld.getConnectionState(device); 663 } 664 } 665 } 666 667 /** 668 * Set connection policy of the profile and connects it if connectionPolicy is {@link 669 * BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is {@link 670 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 671 * 672 * <p>The device should already be paired. Connection policy can be one of: {@link 673 * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link 674 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 675 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 676 * 677 * @param device Paired bluetooth device 678 * @param connectionPolicy is the connection policy to set to for this profile 679 * @return true if connectionPolicy is set, false on error 680 */ setConnectionPolicy(BluetoothDevice device, int connectionPolicy)681 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 682 if (device == null) { 683 throw new IllegalArgumentException("Null device"); 684 } 685 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 686 687 if (!mDatabaseManager.setProfileConnectionPolicy( 688 device, BluetoothProfile.PBAP_CLIENT, connectionPolicy)) { 689 return false; 690 } 691 if (connectionPolicy == CONNECTION_POLICY_ALLOWED) { 692 connect(device); 693 } else if (connectionPolicy == CONNECTION_POLICY_FORBIDDEN) { 694 disconnect(device); 695 } 696 return true; 697 } 698 699 /** 700 * Get the connection policy of the profile. 701 * 702 * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 703 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 704 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 705 * 706 * @param device Bluetooth device 707 * @return connection policy of the device 708 */ getConnectionPolicy(BluetoothDevice device)709 public int getConnectionPolicy(BluetoothDevice device) { 710 if (device == null) { 711 throw new IllegalArgumentException("Null device"); 712 } 713 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT); 714 } 715 716 // ********************************************************************************************* 717 // * Pre-Refactor Methods 718 // ********************************************************************************************* 719 720 @VisibleForTesting PbapClientService(AdapterService adapterService, PbapClientAccountManager accountManager)721 PbapClientService(AdapterService adapterService, PbapClientAccountManager accountManager) { 722 super(requireNonNull(adapterService)); 723 mDatabaseManager = requireNonNull(adapterService.getDatabase()); 724 mHandler = new Handler(Looper.getMainLooper()); 725 726 if (Flags.pbapClientStorageRefactor()) { 727 throw new IllegalStateException("pbapClientStorageRefactor: Invalid constructor call"); 728 } 729 730 mPbapClientAccountManager = requireNonNull(accountManager); 731 mPbapClientContactsStorage = null; 732 mPbapClientStateMachineMap = null; 733 734 setComponentAvailable(AUTHENTICATOR_SERVICE, true); 735 736 mPbapClientAccountManager.start(); 737 738 registerSdpRecord(); 739 setPbapClientService(this); 740 } 741 cleanupDevice(BluetoothDevice device)742 void cleanupDevice(BluetoothDevice device) { 743 if (Flags.pbapClientStorageRefactor()) { 744 Log.w(TAG, "This should not be used in this configuration"); 745 } 746 747 Log.d(TAG, "Cleanup device: " + device); 748 synchronized (mPbapClientStateMachineOldMap) { 749 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device); 750 if (smOld != null) { 751 mPbapClientStateMachineOldMap.remove(device); 752 smOld.doQuit(); 753 } 754 } 755 } 756 757 /** 758 * Determine if our account type is available and ready to be interacted with 759 * 760 * @return True is account type is ready, false otherwise 761 */ isAccountTypeReady()762 public boolean isAccountTypeReady() { 763 if (Flags.pbapClientStorageRefactor()) { 764 Log.w(TAG, "This should not be used in this configuration"); 765 } 766 return mPbapClientAccountManager.isAccountTypeInitialized(); 767 } 768 769 /** 770 * Add an account 771 * 772 * @param account The account you wish to add 773 * @return True if the account addition was successful, False otherwise 774 */ addAccount(Account account)775 public boolean addAccount(Account account) { 776 if (Flags.pbapClientStorageRefactor()) { 777 Log.w(TAG, "This should not be used in this configuration"); 778 } 779 return mPbapClientAccountManager.addAccount(account); 780 } 781 782 /** 783 * Remove an account 784 * 785 * @param account The account you wish to remove 786 * @return True if the account removal was successful, False otherwise 787 */ removeAccount(Account account)788 public boolean removeAccount(Account account) { 789 if (Flags.pbapClientStorageRefactor()) { 790 Log.w(TAG, "This should not be used in this configuration"); 791 } 792 return mPbapClientAccountManager.removeAccount(account); 793 } 794 } 795