• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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