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.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 140 // delay initial download until after the user is unlocked to add an account. 141 filter.addAction(Intent.ACTION_USER_UNLOCKED); 142 // To remove call logs when PBAP was never connected while calls were made, 143 // we also listen for HFP to become disconnected. 144 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 145 try { 146 registerReceiver(mPbapBroadcastReceiver, filter); 147 } catch (Exception e) { 148 Log.w(TAG, "Unable to register pbapclient receiver", e); 149 } 150 151 initializeAuthenticationService(); 152 registerSdpRecord(); 153 setPbapClientService(this); 154 return true; 155 } 156 157 @Override stop()158 protected boolean stop() { 159 setPbapClientService(null); 160 cleanUpSdpRecord(); 161 try { 162 unregisterReceiver(mPbapBroadcastReceiver); 163 } catch (Exception e) { 164 Log.w(TAG, "Unable to unregister pbapclient receiver", e); 165 } 166 for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { 167 pbapClientStateMachine.doQuit(); 168 } 169 mPbapClientStateMachineMap.clear(); 170 cleanupAuthenicationService(); 171 setComponentAvailable(AUTHENTICATOR_SERVICE, false); 172 return true; 173 } 174 cleanupDevice(BluetoothDevice device)175 void cleanupDevice(BluetoothDevice device) { 176 if (DBG) Log.d(TAG, "Cleanup device: " + device); 177 synchronized (mPbapClientStateMachineMap) { 178 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 179 if (pbapClientStateMachine != null) { 180 mPbapClientStateMachineMap.remove(device); 181 } 182 } 183 } 184 185 /** 186 * Periodically check if the account framework has recognized our service and will allow us to 187 * interact with our accounts. Notify state machines once our service is ready so we can trigger 188 * account downloads. 189 */ initializeAuthenticationService()190 private void initializeAuthenticationService() { 191 mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); 192 } 193 cleanupAuthenicationService()194 private void cleanupAuthenicationService() { 195 mAuthServiceHandler.removeCallbacks(mCheckAuthService); 196 removeUncleanAccounts(); 197 } 198 199 /** 200 * Determine if our account type is visible to us yet. If it is, then our service is ready and 201 * our account type is ready to use. 202 * 203 * Make a placeholder device account and determine our visibility relative to it. Note that this 204 * function uses the same restrictions are the other add and remove functions, but is *also* 205 * available to all system apps instead of throwing a runtime SecurityException. 206 */ isAuthenticationServiceReady()207 protected boolean isAuthenticationServiceReady() { 208 Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); 209 AccountManager accountManager = AccountManager.get(this); 210 int visibility = accountManager.getAccountVisibility(account, getPackageName()); 211 if (DBG) { 212 Log.d(TAG, "Checking visibility, visibility=" + visibility); 213 } 214 return visibility == AccountManager.VISIBILITY_VISIBLE 215 || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; 216 } 217 removeUncleanAccounts()218 private void removeUncleanAccounts() { 219 if (!isAuthenticationServiceReady()) { 220 Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); 221 return; 222 } 223 224 // Find all accounts that match the type "pbap" and delete them. 225 AccountManager accountManager = AccountManager.get(this); 226 Account[] accounts = 227 accountManager.getAccountsByType(getString(R.string.pbap_account_type)); 228 if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts"); 229 for (Account acc : accounts) { 230 Log.w(TAG, "Deleting " + acc); 231 try { 232 getContentResolver().delete(CallLog.Calls.CONTENT_URI, 233 CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name}); 234 } catch (IllegalArgumentException e) { 235 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 236 } 237 // The device ID is the name of the account. 238 accountManager.removeAccountExplicitly(acc); 239 } 240 } 241 removeHfpCallLog(String accountName, Context context)242 private void removeHfpCallLog(String accountName, Context context) { 243 if (DBG) Log.d(TAG, "Removing call logs from " + accountName); 244 // Delete call logs belonging to accountName==BD_ADDR that also match 245 // component name "hfpclient". 246 ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class); 247 String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND " 248 + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?"; 249 String[] selectionArgs = new String[]{accountName, componentName.flattenToString()}; 250 try { 251 BluetoothMethodProxy.getInstance().contentResolverDelete(getContentResolver(), 252 CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); 253 } catch (IllegalArgumentException e) { 254 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); 255 } 256 } 257 registerSdpRecord()258 private void registerSdpRecord() { 259 SdpManager sdpManager = SdpManager.getDefaultManager(); 260 if (sdpManager == null) { 261 Log.e(TAG, "SdpManager is null"); 262 return; 263 } 264 mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME, 265 PbapClientConnectionHandler.PBAP_V1_2); 266 } 267 cleanUpSdpRecord()268 private void cleanUpSdpRecord() { 269 if (mSdpHandle < 0) { 270 Log.e(TAG, "cleanUpSdpRecord, SDP record never created"); 271 return; 272 } 273 int sdpHandle = mSdpHandle; 274 mSdpHandle = -1; 275 SdpManager sdpManager = SdpManager.getDefaultManager(); 276 if (sdpManager == null) { 277 Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle); 278 return; 279 } 280 Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle); 281 if (!sdpManager.removeSdpRecord(sdpHandle)) { 282 Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle); 283 } 284 } 285 286 287 @VisibleForTesting 288 class PbapBroadcastReceiver extends BroadcastReceiver { 289 @Override onReceive(Context context, Intent intent)290 public void onReceive(Context context, Intent intent) { 291 String action = intent.getAction(); 292 if (DBG) Log.v(TAG, "onReceive" + action); 293 if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 294 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 295 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { 296 disconnect(device); 297 } 298 } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { 299 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 300 stateMachine.tryDownloadIfConnected(); 301 } 302 } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { 303 // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. 304 // However, if PBAP was never connected/enabled in the first place, and calls are 305 // made over HFP, these calllogs will not be removed when the device disconnects. 306 // This code ensures callogs are still removed in this case. 307 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 308 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 309 310 if (newState == BluetoothProfile.STATE_DISCONNECTED) { 311 if (DBG) { 312 Log.d(TAG, "Received intent to disconnect HFP with " + device); 313 } 314 // HFP client stores entries in calllog.db by BD_ADDR and component name 315 removeHfpCallLog(device.getAddress(), context); 316 } 317 } 318 } 319 } 320 321 /** 322 * Handler for incoming service calls 323 */ 324 @VisibleForTesting 325 static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub 326 implements IProfileServiceBinder { 327 private PbapClientService mService; 328 BluetoothPbapClientBinder(PbapClientService svc)329 BluetoothPbapClientBinder(PbapClientService svc) { 330 mService = svc; 331 } 332 333 @Override cleanup()334 public void cleanup() { 335 mService = null; 336 } 337 338 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)339 private PbapClientService getService(AttributionSource source) { 340 if (Utils.isInstrumentationTestMode()) { 341 return mService; 342 } 343 if (!Utils.checkServiceAvailable(mService, TAG) 344 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 345 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 346 return null; 347 } 348 return mService; 349 } 350 351 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)352 public void connect(BluetoothDevice device, AttributionSource source, 353 SynchronousResultReceiver receiver) { 354 if (DBG) Log.d(TAG, "PbapClient Binder connect "); 355 try { 356 PbapClientService service = getService(source); 357 boolean defaultValue = false; 358 if (service != null) { 359 defaultValue = service.connect(device); 360 } else { 361 Log.e(TAG, "PbapClient Binder connect no service"); 362 } 363 receiver.send(defaultValue); 364 } catch (RuntimeException e) { 365 receiver.propagateException(e); 366 } 367 } 368 369 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)370 public void disconnect(BluetoothDevice device, AttributionSource source, 371 SynchronousResultReceiver receiver) { 372 try { 373 PbapClientService service = getService(source); 374 boolean defaultValue = false; 375 if (service != null) { 376 defaultValue = service.disconnect(device); 377 } 378 receiver.send(defaultValue); 379 } catch (RuntimeException e) { 380 receiver.propagateException(e); 381 } 382 } 383 384 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)385 public void getConnectedDevices(AttributionSource source, 386 SynchronousResultReceiver receiver) { 387 try { 388 PbapClientService service = getService(source); 389 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 390 if (service != null) { 391 defaultValue = service.getConnectedDevices(); 392 } 393 receiver.send(defaultValue); 394 } catch (RuntimeException e) { 395 receiver.propagateException(e); 396 } 397 } 398 399 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)400 public void getDevicesMatchingConnectionStates(int[] states, 401 AttributionSource source, SynchronousResultReceiver receiver) { 402 try { 403 PbapClientService service = getService(source); 404 List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0); 405 if (service != null) { 406 defaultValue = service.getDevicesMatchingConnectionStates(states); 407 } 408 receiver.send(defaultValue); 409 } catch (RuntimeException e) { 410 receiver.propagateException(e); 411 } 412 } 413 414 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)415 public void getConnectionState(BluetoothDevice device, AttributionSource source, 416 SynchronousResultReceiver receiver) { 417 try { 418 PbapClientService service = getService(source); 419 int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 420 if (service != null) { 421 defaultValue = service.getConnectionState(device); 422 } 423 receiver.send(defaultValue); 424 } catch (RuntimeException e) { 425 receiver.propagateException(e); 426 } 427 } 428 429 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)430 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 431 AttributionSource source, SynchronousResultReceiver receiver) { 432 try { 433 PbapClientService service = getService(source); 434 boolean defaultValue = false; 435 if (service != null) { 436 defaultValue = service.setConnectionPolicy(device, connectionPolicy); 437 } 438 receiver.send(defaultValue); 439 } catch (RuntimeException e) { 440 receiver.propagateException(e); 441 } 442 } 443 444 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)445 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 446 SynchronousResultReceiver receiver) { 447 try { 448 PbapClientService service = getService(source); 449 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 450 if (service != null) { 451 defaultValue = service.getConnectionPolicy(device); 452 } 453 receiver.send(defaultValue); 454 } catch (RuntimeException e) { 455 receiver.propagateException(e); 456 } 457 } 458 459 460 } 461 462 // API methods getPbapClientService()463 public static synchronized PbapClientService getPbapClientService() { 464 if (sPbapClientService == null) { 465 Log.w(TAG, "getPbapClientService(): service is null"); 466 return null; 467 } 468 if (!sPbapClientService.isAvailable()) { 469 Log.w(TAG, "getPbapClientService(): service is not available"); 470 return null; 471 } 472 return sPbapClientService; 473 } 474 475 @VisibleForTesting setPbapClientService(PbapClientService instance)476 static synchronized void setPbapClientService(PbapClientService instance) { 477 if (VDBG) { 478 Log.v(TAG, "setPbapClientService(): set to: " + instance); 479 } 480 sPbapClientService = instance; 481 } 482 483 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)484 public boolean connect(BluetoothDevice device) { 485 if (device == null) { 486 throw new IllegalArgumentException("Null device"); 487 } 488 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 489 "Need BLUETOOTH_PRIVILEGED permission"); 490 if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress()); 491 if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 492 return false; 493 } 494 synchronized (mPbapClientStateMachineMap) { 495 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 496 if (pbapClientStateMachine == null 497 && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) { 498 pbapClientStateMachine = new PbapClientStateMachine(this, device); 499 pbapClientStateMachine.start(); 500 mPbapClientStateMachineMap.put(device, pbapClientStateMachine); 501 return true; 502 } else { 503 Log.w(TAG, "Received connect request while already connecting/connected."); 504 return false; 505 } 506 } 507 } 508 509 /** 510 * Disconnects the pbap client profile from the passed in device 511 * 512 * @param device is the device with which we will disconnect the pbap client profile 513 * @return true if we disconnected the pbap client profile, false otherwise 514 */ 515 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)516 public boolean disconnect(BluetoothDevice device) { 517 if (device == null) { 518 throw new IllegalArgumentException("Null device"); 519 } 520 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 521 "Need BLUETOOTH_PRIVILEGED permission"); 522 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 523 if (pbapClientStateMachine != null) { 524 pbapClientStateMachine.disconnect(device); 525 return true; 526 } else { 527 Log.w(TAG, "disconnect() called on unconnected device."); 528 return false; 529 } 530 } 531 getConnectedDevices()532 public List<BluetoothDevice> getConnectedDevices() { 533 int[] desiredStates = {BluetoothProfile.STATE_CONNECTED}; 534 return getDevicesMatchingConnectionStates(desiredStates); 535 } 536 537 @VisibleForTesting getDevicesMatchingConnectionStates(int[] states)538 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 539 List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0); 540 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry : 541 mPbapClientStateMachineMap 542 .entrySet()) { 543 int currentDeviceState = stateMachineEntry.getValue().getConnectionState(); 544 for (int state : states) { 545 if (currentDeviceState == state) { 546 deviceList.add(stateMachineEntry.getKey()); 547 break; 548 } 549 } 550 } 551 return deviceList; 552 } 553 554 /** 555 * Get the current connection state of the profile 556 * 557 * @param device is the remote bluetooth device 558 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 559 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 560 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 561 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 562 */ getConnectionState(BluetoothDevice device)563 public int getConnectionState(BluetoothDevice device) { 564 if (device == null) { 565 throw new IllegalArgumentException("Null device"); 566 } 567 PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device); 568 if (pbapClientStateMachine == null) { 569 return BluetoothProfile.STATE_DISCONNECTED; 570 } else { 571 return pbapClientStateMachine.getConnectionState(device); 572 } 573 } 574 575 /** 576 * Set connection policy of the profile and connects it if connectionPolicy is 577 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 578 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 579 * 580 * <p> The device should already be paired. 581 * Connection policy can be one of: 582 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 583 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 584 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 585 * 586 * @param device Paired bluetooth device 587 * @param connectionPolicy is the connection policy to set to for this profile 588 * @return true if connectionPolicy is set, false on error 589 */ 590 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)591 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 592 if (device == null) { 593 throw new IllegalArgumentException("Null device"); 594 } 595 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 596 "Need BLUETOOTH_PRIVILEGED permission"); 597 if (DBG) { 598 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 599 } 600 601 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT, 602 connectionPolicy)) { 603 return false; 604 } 605 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 606 connect(device); 607 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 608 disconnect(device); 609 } 610 return true; 611 } 612 613 /** 614 * Get the connection policy of the profile. 615 * 616 * <p> The connection policy can be any of: 617 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 618 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 619 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 620 * 621 * @param device Bluetooth device 622 * @return connection policy of the device 623 * @hide 624 */ 625 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)626 public int getConnectionPolicy(BluetoothDevice device) { 627 if (device == null) { 628 throw new IllegalArgumentException("Null device"); 629 } 630 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 631 "Need BLUETOOTH_PRIVILEGED permission"); 632 return mDatabaseManager 633 .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT); 634 } 635 636 @Override dump(StringBuilder sb)637 public void dump(StringBuilder sb) { 638 super.dump(sb); 639 ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); 640 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { 641 stateMachine.dump(sb); 642 } 643 } 644 } 645