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