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 android.accounts.Account; 20 import android.accounts.AccountManager; 21 import android.annotation.RequiresPermission; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadsetClient; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.IBluetoothPbapClient; 26 import android.content.AttributionSource; 27 import android.content.BroadcastReceiver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.os.Handler; 33 import android.provider.CallLog; 34 import android.sysprop.BluetoothProperties; 35 import android.util.Log; 36 37 import com.android.bluetooth.BluetoothMethodProxy; 38 import com.android.bluetooth.R; 39 import com.android.bluetooth.Utils; 40 import com.android.bluetooth.btservice.AdapterService; 41 import com.android.bluetooth.btservice.ProfileService; 42 import com.android.bluetooth.btservice.storage.DatabaseManager; 43 import com.android.bluetooth.hfpclient.HfpClientConnectionService; 44 import com.android.bluetooth.sdp.SdpManager; 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.modules.utils.SynchronousResultReceiver; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 import java.util.concurrent.ConcurrentHashMap; 53 54 /** 55 * Provides Bluetooth Phone Book Access Profile Client profile. 56 * 57 * @hide 58 */ 59 public class PbapClientService extends ProfileService { 60 private static final boolean DBG = com.android.bluetooth.pbapclient.Utils.DBG; 61 private static final boolean VDBG = com.android.bluetooth.pbapclient.Utils.VDBG; 62 63 private static final String TAG = "PbapClientService"; 64 private static final String SERVICE_NAME = "Phonebook Access PCE"; 65 66 /** 67 * The component names for the owned authenticator service 68 */ 69 private static final String AUTHENTICATOR_SERVICE = 70 AuthenticationService.class.getCanonicalName(); 71 72 // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. 73 private static final int MAXIMUM_DEVICES = 10; 74 @VisibleForTesting 75 Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = 76 new ConcurrentHashMap<>(); 77 private static PbapClientService sPbapClientService; 78 @VisibleForTesting 79 PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver(); 80 private int mSdpHandle = -1; 81 82 private DatabaseManager mDatabaseManager; 83 84 /** 85 * There's an ~1-2 second latency between when our Authentication service is set as available to 86 * the system and when the Authentication/Account framework code will recognize it and allow us 87 * to alter accounts. In lieu of the Accounts team dealing with this race condition, we're going 88 * to periodically poll over 3 seconds until our accounts are visible, remove old accounts, and 89 * then notify device state machines that they can create accounts and download contacts. 90 */ 91 // TODO(233361365): Remove this pattern when the framework solves their race condition 92 private static final int ACCOUNT_VISIBILITY_CHECK_MS = 500; 93 private static final int ACCOUNT_VISIBILITY_CHECK_TRIES_MAX = 6; 94 private int mAccountVisibilityCheckTries = 0; 95 private final Handler mAuthServiceHandler = new Handler(); 96 private final Runnable mCheckAuthService = new Runnable() { 97 @Override 98 public void run() { 99 // If our accounts are finally visible to use, clean up old ones and tell devices they 100 // can issue downloads if they're ready. Otherwise, wait and try again. 101 if (isAuthenticationServiceReady()) { 102 Log.i(TAG, "Service ready! Clean up old accounts and try contacts downloads"); 103 removeUncleanAccounts(); 104 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 105 stateMachine.tryDownloadIfConnected(); 106 } 107 } else if (mAccountVisibilityCheckTries < ACCOUNT_VISIBILITY_CHECK_TRIES_MAX) { 108 mAccountVisibilityCheckTries += 1; 109 Log.w(TAG, "AccountManager hasn't registered our service yet. Retry " 110 + mAccountVisibilityCheckTries + "/" + ACCOUNT_VISIBILITY_CHECK_TRIES_MAX); 111 mAuthServiceHandler.postDelayed(this, ACCOUNT_VISIBILITY_CHECK_MS); 112 } else { 113 Log.e(TAG, "Failed to register Authenication Service and get account visibility"); 114 } 115 } 116 }; 117 isEnabled()118 public static boolean isEnabled() { 119 return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); 120 } 121 122 @Override initBinder()123 public IProfileServiceBinder initBinder() { 124 return new BluetoothPbapClientBinder(this); 125 } 126 127 @Override start()128 protected boolean start() { 129 if (VDBG) { 130 Log.v(TAG, "onStart"); 131 } 132 133 mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), 134 "DatabaseManager cannot be null when PbapClientService starts"); 135 136 setComponentAvailable(AUTHENTICATOR_SERVICE, true); 137 138 IntentFilter filter = new IntentFilter(); 139 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 140 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 141 // delay initial download until after the user is unlocked to add an account. 142 filter.addAction(Intent.ACTION_USER_UNLOCKED); 143 // To remove call logs when PBAP was never connected while calls were made, 144 // we also listen for HFP to become disconnected. 145 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 146 try { 147 registerReceiver(mPbapBroadcastReceiver, filter); 148 } catch (Exception e) { 149 Log.w(TAG, "Unable to register pbapclient receiver", e); 150 } 151 152 initializeAuthenticationService(); 153 registerSdpRecord(); 154 setPbapClientService(this); 155 return true; 156 } 157 158 @Override stop()159 protected boolean stop() { 160 setPbapClientService(null); 161 cleanUpSdpRecord(); 162 try { 163 unregisterReceiver(mPbapBroadcastReceiver); 164 } catch (Exception e) { 165 Log.w(TAG, "Unable to unregister pbapclient receiver", e); 166 } 167 for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { 168 pbapClientStateMachine.doQuit(); 169 } 170 mPbapClientStateMachineMap.clear(); 171 cleanupAuthenicationService(); 172 setComponentAvailable(AUTHENTICATOR_SERVICE, false); 173 return true; 174 } 175 cleanupDevice(BluetoothDevice device)176 void cleanupDevice(BluetoothDevice device) { 177 if (DBG) Log.d(TAG, "Cleanup device: " + device); 178 synchronized (mPbapClientStateMachineMap) { 179 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 180 if (pbapClientStateMachine != null) { 181 mPbapClientStateMachineMap.remove(device); 182 } 183 } 184 } 185 186 /** 187 * Periodically check if the account framework has recognized our service and will allow us to 188 * interact with our accounts. Notify state machines once our service is ready so we can trigger 189 * account downloads. 190 */ initializeAuthenticationService()191 private void initializeAuthenticationService() { 192 mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); 193 } 194 cleanupAuthenicationService()195 private void cleanupAuthenicationService() { 196 mAuthServiceHandler.removeCallbacks(mCheckAuthService); 197 removeUncleanAccounts(); 198 } 199 200 /** 201 * Determine if our account type is visible to us yet. If it is, then our service is ready and 202 * our account type is ready to use. 203 * 204 * Make a placeholder device account and determine our visibility relative to it. Note that this 205 * function uses the same restrictions are the other add and remove functions, but is *also* 206 * available to all system apps instead of throwing a runtime SecurityException. 207 */ isAuthenticationServiceReady()208 protected boolean isAuthenticationServiceReady() { 209 Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); 210 AccountManager accountManager = AccountManager.get(this); 211 int visibility = accountManager.getAccountVisibility(account, getPackageName()); 212 if (DBG) { 213 Log.d(TAG, "Checking visibility, visibility=" + visibility); 214 } 215 return visibility == AccountManager.VISIBILITY_VISIBLE 216 || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; 217 } 218 removeUncleanAccounts()219 private void removeUncleanAccounts() { 220 if (!isAuthenticationServiceReady()) { 221 Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); 222 return; 223 } 224 225 // Find all accounts that match the type "pbap" and delete them. 226 AccountManager accountManager = AccountManager.get(this); 227 Account[] accounts = 228 accountManager.getAccountsByType(getString(R.string.pbap_account_type)); 229 if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts"); 230 for (Account acc : accounts) { 231 Log.w(TAG, "Deleting " + acc); 232 try { 233 getContentResolver().delete(CallLog.Calls.CONTENT_URI, 234 CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name}); 235 } catch (IllegalArgumentException e) { 236 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 237 } 238 // The device ID is the name of the account. 239 accountManager.removeAccountExplicitly(acc); 240 } 241 } 242 removeHfpCallLog(String accountName, Context context)243 private void removeHfpCallLog(String accountName, Context context) { 244 if (DBG) Log.d(TAG, "Removing call logs from " + accountName); 245 // Delete call logs belonging to accountName==BD_ADDR that also match 246 // component name "hfpclient". 247 ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class); 248 String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND " 249 + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?"; 250 String[] selectionArgs = new String[]{accountName, componentName.flattenToString()}; 251 try { 252 BluetoothMethodProxy.getInstance().contentResolverDelete(getContentResolver(), 253 CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); 254 } catch (IllegalArgumentException e) { 255 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 256 } 257 } 258 registerSdpRecord()259 private void registerSdpRecord() { 260 SdpManager sdpManager = SdpManager.getDefaultManager(); 261 if (sdpManager == null) { 262 Log.e(TAG, "SdpManager is null"); 263 return; 264 } 265 mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME, 266 PbapClientConnectionHandler.PBAP_V1_2); 267 } 268 cleanUpSdpRecord()269 private void cleanUpSdpRecord() { 270 if (mSdpHandle < 0) { 271 Log.e(TAG, "cleanUpSdpRecord, SDP record never created"); 272 return; 273 } 274 int sdpHandle = mSdpHandle; 275 mSdpHandle = -1; 276 SdpManager sdpManager = SdpManager.getDefaultManager(); 277 if (sdpManager == null) { 278 Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle); 279 return; 280 } 281 Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle); 282 if (!sdpManager.removeSdpRecord(sdpHandle)) { 283 Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle); 284 } 285 } 286 287 288 @VisibleForTesting 289 class PbapBroadcastReceiver extends BroadcastReceiver { 290 @Override onReceive(Context context, Intent intent)291 public void onReceive(Context context, Intent intent) { 292 String action = intent.getAction(); 293 if (DBG) Log.v(TAG, "onReceive" + action); 294 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 295 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 296 int transport = 297 intent.getIntExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.ERROR); 298 299 Log.i(TAG, "Received ACL disconnection event, device=" + device.toString() 300 + ", transport=" + transport); 301 302 if (transport != BluetoothDevice.TRANSPORT_BREDR) { 303 return; 304 } 305 306 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { 307 disconnect(device); 308 } 309 } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { 310 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 311 stateMachine.tryDownloadIfConnected(); 312 } 313 } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { 314 // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. 315 // However, if PBAP was never connected/enabled in the first place, and calls are 316 // made over HFP, these calllogs will not be removed when the device disconnects. 317 // This code ensures callogs are still removed in this case. 318 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 319 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 320 321 if (newState == BluetoothProfile.STATE_DISCONNECTED) { 322 if (DBG) { 323 Log.d(TAG, "Received intent to disconnect HFP with " + device); 324 } 325 // HFP client stores entries in calllog.db by BD_ADDR and component name 326 removeHfpCallLog(device.getAddress(), context); 327 } 328 } 329 } 330 } 331 332 /** 333 * Handler for incoming service calls 334 */ 335 @VisibleForTesting 336 static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub 337 implements IProfileServiceBinder { 338 private PbapClientService mService; 339 BluetoothPbapClientBinder(PbapClientService svc)340 BluetoothPbapClientBinder(PbapClientService svc) { 341 mService = svc; 342 } 343 344 @Override cleanup()345 public void cleanup() { 346 mService = null; 347 } 348 349 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)350 private PbapClientService getService(AttributionSource source) { 351 if (Utils.isInstrumentationTestMode()) { 352 return mService; 353 } 354 if (!Utils.checkServiceAvailable(mService, TAG) 355 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 356 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 357 return null; 358 } 359 return mService; 360 } 361 362 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)363 public void connect(BluetoothDevice device, AttributionSource source, 364 SynchronousResultReceiver receiver) { 365 if (DBG) Log.d(TAG, "PbapClient Binder connect "); 366 try { 367 PbapClientService service = getService(source); 368 boolean defaultValue = false; 369 if (service != null) { 370 defaultValue = service.connect(device); 371 } else { 372 Log.e(TAG, "PbapClient Binder connect no service"); 373 } 374 receiver.send(defaultValue); 375 } catch (RuntimeException e) { 376 receiver.propagateException(e); 377 } 378 } 379 380 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)381 public void disconnect(BluetoothDevice device, AttributionSource source, 382 SynchronousResultReceiver receiver) { 383 try { 384 PbapClientService service = getService(source); 385 boolean defaultValue = false; 386 if (service != null) { 387 defaultValue = service.disconnect(device); 388 } 389 receiver.send(defaultValue); 390 } catch (RuntimeException e) { 391 receiver.propagateException(e); 392 } 393 } 394 395 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)396 public void getConnectedDevices(AttributionSource source, 397 SynchronousResultReceiver receiver) { 398 try { 399 PbapClientService service = getService(source); 400 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 401 if (service != null) { 402 defaultValue = service.getConnectedDevices(); 403 } 404 receiver.send(defaultValue); 405 } catch (RuntimeException e) { 406 receiver.propagateException(e); 407 } 408 } 409 410 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)411 public void getDevicesMatchingConnectionStates(int[] states, 412 AttributionSource source, SynchronousResultReceiver receiver) { 413 try { 414 PbapClientService service = getService(source); 415 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 416 if (service != null) { 417 defaultValue = service.getDevicesMatchingConnectionStates(states); 418 } 419 receiver.send(defaultValue); 420 } catch (RuntimeException e) { 421 receiver.propagateException(e); 422 } 423 } 424 425 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)426 public void getConnectionState(BluetoothDevice device, AttributionSource source, 427 SynchronousResultReceiver receiver) { 428 try { 429 PbapClientService service = getService(source); 430 int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 431 if (service != null) { 432 defaultValue = service.getConnectionState(device); 433 } 434 receiver.send(defaultValue); 435 } catch (RuntimeException e) { 436 receiver.propagateException(e); 437 } 438 } 439 440 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)441 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 442 AttributionSource source, SynchronousResultReceiver receiver) { 443 try { 444 PbapClientService service = getService(source); 445 boolean defaultValue = false; 446 if (service != null) { 447 defaultValue = service.setConnectionPolicy(device, connectionPolicy); 448 } 449 receiver.send(defaultValue); 450 } catch (RuntimeException e) { 451 receiver.propagateException(e); 452 } 453 } 454 455 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)456 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 457 SynchronousResultReceiver receiver) { 458 try { 459 PbapClientService service = getService(source); 460 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 461 if (service != null) { 462 defaultValue = service.getConnectionPolicy(device); 463 } 464 receiver.send(defaultValue); 465 } catch (RuntimeException e) { 466 receiver.propagateException(e); 467 } 468 } 469 470 471 } 472 473 // API methods getPbapClientService()474 public static synchronized PbapClientService getPbapClientService() { 475 if (sPbapClientService == null) { 476 Log.w(TAG, "getPbapClientService(): service is null"); 477 return null; 478 } 479 if (!sPbapClientService.isAvailable()) { 480 Log.w(TAG, "getPbapClientService(): service is not available"); 481 return null; 482 } 483 return sPbapClientService; 484 } 485 486 @VisibleForTesting setPbapClientService(PbapClientService instance)487 static synchronized void setPbapClientService(PbapClientService instance) { 488 if (VDBG) { 489 Log.v(TAG, "setPbapClientService(): set to: " + instance); 490 } 491 sPbapClientService = instance; 492 } 493 494 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)495 public boolean connect(BluetoothDevice device) { 496 if (device == null) { 497 throw new IllegalArgumentException("Null device"); 498 } 499 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 500 "Need BLUETOOTH_PRIVILEGED permission"); 501 if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress()); 502 if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 503 return false; 504 } 505 synchronized (mPbapClientStateMachineMap) { 506 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 507 if (pbapClientStateMachine == null 508 && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) { 509 pbapClientStateMachine = new PbapClientStateMachine(this, device); 510 pbapClientStateMachine.start(); 511 mPbapClientStateMachineMap.put(device, pbapClientStateMachine); 512 return true; 513 } else { 514 Log.w(TAG, "Received connect request while already connecting/connected."); 515 return false; 516 } 517 } 518 } 519 520 /** 521 * Disconnects the pbap client profile from the passed in device 522 * 523 * @param device is the device with which we will disconnect the pbap client profile 524 * @return true if we disconnected the pbap client profile, false otherwise 525 */ 526 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)527 public boolean disconnect(BluetoothDevice device) { 528 if (device == null) { 529 throw new IllegalArgumentException("Null device"); 530 } 531 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 532 "Need BLUETOOTH_PRIVILEGED permission"); 533 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 534 if (pbapClientStateMachine != null) { 535 pbapClientStateMachine.disconnect(device); 536 return true; 537 } else { 538 Log.w(TAG, "disconnect() called on unconnected device."); 539 return false; 540 } 541 } 542 getConnectedDevices()543 public List<BluetoothDevice> getConnectedDevices() { 544 int[] desiredStates = {BluetoothProfile.STATE_CONNECTED}; 545 return getDevicesMatchingConnectionStates(desiredStates); 546 } 547 548 @VisibleForTesting getDevicesMatchingConnectionStates(int[] states)549 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 550 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); 551 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : 552 mPbapClientStateMachineMap 553 .entrySet()) { 554 int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); 555 for (int state : states) { 556 if (currentDeviceState == state) { 557 deviceList.add(stateMachineEntry.getKey()); 558 break; 559 } 560 } 561 } 562 return deviceList; 563 } 564 565 /** 566 * Get the current connection state of the profile 567 * 568 * @param device is the remote bluetooth device 569 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 570 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 571 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 572 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 573 */ getConnectionState(BluetoothDevice device)574 public int getConnectionState(BluetoothDevice device) { 575 if (device == null) { 576 throw new IllegalArgumentException("Null device"); 577 } 578 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 579 if (pbapClientStateMachine == null) { 580 return BluetoothProfile.STATE_DISCONNECTED; 581 } else { 582 return pbapClientStateMachine.getConnectionState(device); 583 } 584 } 585 586 /** 587 * Set connection policy of the profile and connects it if connectionPolicy is 588 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 589 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 590 * 591 * <p> The device should already be paired. 592 * Connection policy can be one of: 593 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 594 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 595 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 596 * 597 * @param device Paired bluetooth device 598 * @param connectionPolicy is the connection policy to set to for this profile 599 * @return true if connectionPolicy is set, false on error 600 */ 601 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)602 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 603 if (device == null) { 604 throw new IllegalArgumentException("Null device"); 605 } 606 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 607 "Need BLUETOOTH_PRIVILEGED permission"); 608 if (DBG) { 609 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 610 } 611 612 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT, 613 connectionPolicy)) { 614 return false; 615 } 616 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 617 connect(device); 618 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 619 disconnect(device); 620 } 621 return true; 622 } 623 624 /** 625 * Get the connection policy of the profile. 626 * 627 * <p> The connection policy can be any of: 628 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 629 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 630 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 631 * 632 * @param device Bluetooth device 633 * @return connection policy of the device 634 * @hide 635 */ 636 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)637 public int getConnectionPolicy(BluetoothDevice device) { 638 if (device == null) { 639 throw new IllegalArgumentException("Null device"); 640 } 641 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 642 "Need BLUETOOTH_PRIVILEGED permission"); 643 return mDatabaseManager 644 .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT); 645 } 646 647 @Override dump(StringBuilder sb)648 public void dump(StringBuilder sb) { 649 super.dump(sb); 650 ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); 651 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 652 stateMachine.dump(sb); 653 } 654 } 655 } 656