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