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