• 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 static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
21 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
22 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
23 
24 import static java.util.Objects.requireNonNull;
25 
26 import android.accounts.Account;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.BluetoothUuid;
30 import android.bluetooth.SdpPseRecord;
31 import android.content.ComponentName;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.Looper;
35 import android.os.ParcelUuid;
36 import android.os.Parcelable;
37 import android.provider.CallLog;
38 import android.sysprop.BluetoothProperties;
39 import android.util.Log;
40 
41 import com.android.bluetooth.btservice.AdapterService;
42 import com.android.bluetooth.btservice.ProfileService;
43 import com.android.bluetooth.btservice.storage.DatabaseManager;
44 import com.android.bluetooth.flags.Flags;
45 import com.android.bluetooth.hfpclient.HfpClientConnectionService;
46 import com.android.bluetooth.sdp.SdpManagerNativeInterface;
47 import com.android.internal.annotations.VisibleForTesting;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.concurrent.ConcurrentHashMap;
53 
54 /** Provides Bluetooth Phone Book Access Profile Client profile. */
55 public class PbapClientService extends ProfileService {
56     private static final String TAG = PbapClientService.class.getSimpleName();
57 
58     private static final String SERVICE_NAME = "Phonebook Access PCE";
59 
60     /** The component names for the owned authenticator service */
61     private static final String AUTHENTICATOR_SERVICE =
62             PbapClientAccountAuthenticatorService.class.getCanonicalName();
63 
64     // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
65     private static final int MAXIMUM_DEVICES = 10;
66 
67     @VisibleForTesting
68     final Map<BluetoothDevice, PbapClientStateMachineOld> mPbapClientStateMachineOldMap =
69             new ConcurrentHashMap<>();
70 
71     private static PbapClientService sPbapClientService;
72     private final PbapClientContactsStorage mPbapClientContactsStorage;
73     private final PbapClientAccountManager mPbapClientAccountManager;
74     private final DatabaseManager mDatabaseManager;
75     private final Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap;
76     private final Handler mHandler;
77 
78     private int mSdpHandle = -1;
79 
80     class PbapClientStateMachineCallback implements PbapClientStateMachine.Callback {
81         private final BluetoothDevice mDevice;
82 
PbapClientStateMachineCallback(BluetoothDevice device)83         PbapClientStateMachineCallback(BluetoothDevice device) {
84             mDevice = device;
85         }
86 
87         @Override
onConnectionStateChanged(int oldState, int newState)88         public void onConnectionStateChanged(int oldState, int newState) {
89             Log.v(
90                     TAG,
91                     "Device connection state changed, device="
92                             + mDevice
93                             + ", old="
94                             + oldState
95                             + ", new="
96                             + newState);
97             if (oldState != newState && newState == STATE_DISCONNECTED) {
98                 removeDevice(mDevice);
99             }
100         }
101     }
102 
103     class PbapClientAccountManagerCallback implements PbapClientAccountManager.Callback {
104         @Override
onAccountsChanged(List<Account> oldAccounts, List<Account> newAccounts)105         public void onAccountsChanged(List<Account> oldAccounts, List<Account> newAccounts) {
106             Log.i(TAG, "onAccountsChanged: old=" + oldAccounts + ", new=" + newAccounts);
107             if (oldAccounts == null) {
108                 removeUncleanAccounts();
109                 for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) {
110                     smOld.tryDownloadIfConnected();
111                 }
112             }
113         }
114     }
115 
PbapClientService(AdapterService adapterService)116     public PbapClientService(AdapterService adapterService) {
117         super(requireNonNull(adapterService));
118         mDatabaseManager = requireNonNull(adapterService.getDatabase());
119         mHandler = new Handler(Looper.getMainLooper());
120 
121         if (Flags.pbapClientStorageRefactor()) {
122             mPbapClientContactsStorage = new PbapClientContactsStorage(adapterService);
123             mPbapClientAccountManager = null;
124             mPbapClientStateMachineMap = new ConcurrentHashMap<>();
125             mPbapClientContactsStorage.start();
126         } else {
127             mPbapClientAccountManager =
128                     new PbapClientAccountManager(
129                             adapterService, new PbapClientAccountManagerCallback());
130             mPbapClientContactsStorage = null;
131             mPbapClientStateMachineMap = null;
132             mPbapClientAccountManager.start();
133         }
134 
135         setComponentAvailable(AUTHENTICATOR_SERVICE, true);
136 
137         registerSdpRecord();
138         setPbapClientService(this);
139     }
140 
141     @VisibleForTesting
PbapClientService( AdapterService adapterService, PbapClientContactsStorage storage, Map<BluetoothDevice, PbapClientStateMachine> deviceMap)142     PbapClientService(
143             AdapterService adapterService,
144             PbapClientContactsStorage storage,
145             Map<BluetoothDevice, PbapClientStateMachine> deviceMap) {
146         super(requireNonNull(adapterService));
147         mDatabaseManager = requireNonNull(adapterService.getDatabase());
148         mHandler = new Handler(Looper.getMainLooper());
149 
150         mPbapClientContactsStorage = storage;
151         mPbapClientStateMachineMap = deviceMap;
152 
153         // For compatibility with tests while we phase the old state machine out
154         mPbapClientAccountManager =
155                 new PbapClientAccountManager(
156                         adapterService, new PbapClientAccountManagerCallback());
157 
158         setComponentAvailable(AUTHENTICATOR_SERVICE, true);
159 
160         if (Flags.pbapClientStorageRefactor()) {
161             mPbapClientContactsStorage.start();
162         } else {
163             mPbapClientAccountManager.start();
164         }
165 
166         registerSdpRecord();
167         setPbapClientService(this);
168     }
169 
isEnabled()170     public static boolean isEnabled() {
171         return BluetoothProperties.isProfilePbapClientEnabled().orElse(false);
172     }
173 
174     @Override
initBinder()175     public IProfileServiceBinder initBinder() {
176         return new PbapClientServiceBinder(this);
177     }
178 
179     @Override
cleanup()180     public void cleanup() {
181         Log.i(TAG, "Cleanup PbapClient Service");
182 
183         setPbapClientService(null);
184         cleanUpSdpRecord();
185 
186         // Unregister SDP event handler and stop all queued messages.
187         mHandler.removeCallbacksAndMessages(null);
188 
189         if (Flags.pbapClientStorageRefactor()) {
190             // Try to bring down all the connections gracefully
191             synchronized (mPbapClientStateMachineMap) {
192                 for (PbapClientStateMachine sm : mPbapClientStateMachineMap.values()) {
193                     sm.disconnect();
194                 }
195                 mPbapClientStateMachineMap.clear();
196             }
197             mPbapClientContactsStorage.stop();
198         } else {
199             for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) {
200                 smOld.doQuit();
201             }
202             mPbapClientStateMachineOldMap.clear();
203             removeUncleanAccounts();
204             mPbapClientAccountManager.stop();
205         }
206 
207         setComponentAvailable(AUTHENTICATOR_SERVICE, false);
208     }
209 
210     /**
211      * Add our PBAP Client SDP record to the device SDP database
212      *
213      * <p>This allows our client to be recognized by the remove device. The record must be cleaned
214      * up when we shutdown.
215      */
registerSdpRecord()216     private void registerSdpRecord() {
217         SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
218         if (!nativeInterface.isAvailable()) {
219             Log.e(TAG, "SdpManagerNativeInterface is not available");
220             return;
221         }
222         mSdpHandle = nativeInterface.createPbapPceRecord(SERVICE_NAME, PbapSdpRecord.VERSION_1_2);
223     }
224 
225     /**
226      * Remove our PBAP Client SDP record from the device SDP database
227      *
228      * <p>Gracefully removes PBAP Client support from our SDP records. Called when shutting down.
229      */
cleanUpSdpRecord()230     private void cleanUpSdpRecord() {
231         if (mSdpHandle < 0) {
232             Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
233             return;
234         }
235         int sdpHandle = mSdpHandle;
236         mSdpHandle = -1;
237         SdpManagerNativeInterface nativeInterface = SdpManagerNativeInterface.getInstance();
238         if (!nativeInterface.isAvailable()) {
239             Log.e(
240                     TAG,
241                     "cleanUpSdpRecord failed, SdpManagerNativeInterface is not available,"
242                             + " sdpHandle="
243                             + sdpHandle);
244             return;
245         }
246         Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
247         if (!nativeInterface.removeSdpRecord(sdpHandle)) {
248             Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
249         }
250     }
251 
getDeviceStateMachine(BluetoothDevice device)252     private PbapClientStateMachine getDeviceStateMachine(BluetoothDevice device) {
253         synchronized (mPbapClientStateMachineMap) {
254             return mPbapClientStateMachineMap.get(device);
255         }
256     }
257 
258     /**
259      * Create a state machine for a device
260      *
261      * <p>PBAP Client connections are always outgoing. This function creates a device state machine
262      * instance, which will manage the connection and data lifecycles of the device.
263      */
addDevice(BluetoothDevice device)264     private boolean addDevice(BluetoothDevice device) {
265         Log.d(TAG, "add device, device=" + device);
266         synchronized (mPbapClientStateMachineMap) {
267             PbapClientStateMachine stateMachine = mPbapClientStateMachineMap.get(device);
268             if (stateMachine == null) {
269                 if (mPbapClientStateMachineMap.size() >= MAXIMUM_DEVICES) {
270                     Log.w(TAG, "Cannot connect " + device + ", too many devices connected already");
271                     return false;
272                 }
273                 stateMachine =
274                         new PbapClientStateMachine(
275                                 device,
276                                 mPbapClientContactsStorage,
277                                 this,
278                                 new PbapClientStateMachineCallback(device));
279                 stateMachine.start();
280                 stateMachine.connect();
281                 mPbapClientStateMachineMap.put(device, stateMachine);
282                 return true;
283             } else {
284                 Log.w(TAG, "Cannot connect " + device + ", already connecting/connected.");
285                 return false;
286             }
287         }
288     }
289 
290     /**
291      * Remove a device state machine, if it exists
292      *
293      * <p>When a device disconnects, we gracefully clean up its state machine instance and drop our
294      * reference to it. State machines cannot be reused, so this must be deleted before a device can
295      * reconnect.
296      */
removeDevice(BluetoothDevice device)297     private void removeDevice(BluetoothDevice device) {
298         Log.d(TAG, "remove device, device=" + device);
299         synchronized (mPbapClientStateMachineMap) {
300             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
301             if (pbapClientStateMachine != null) {
302                 int state = pbapClientStateMachine.getConnectionState();
303                 if (state != STATE_DISCONNECTED) {
304                     Log.w(TAG, "Removing connected device, device=" + device + ", state=" + state);
305                 }
306                 mPbapClientStateMachineMap.remove(device);
307             }
308         }
309     }
310 
311     /**
312      * Clean up any existing accounts.
313      *
314      * <p>This function gets the list of available Pbap Client accounts and deletes them. Deletion
315      * of the account causes Contacts Provider to also delete the associated contacts data. We
316      * separately clean up the call log data associated with a given account too.
317      */
removeUncleanAccounts()318     private void removeUncleanAccounts() {
319         if (Flags.pbapClientStorageRefactor()) {
320             Log.i(TAG, "removeUncleanAccounts: this is the responsibility of contacts storage");
321             return;
322         }
323 
324         List<Account> accounts = mPbapClientAccountManager.getAccounts();
325         Log.i(TAG, "removeUncleanAccounts: Found " + accounts.size() + " accounts");
326 
327         for (Account account : accounts) {
328             Log.d(TAG, "removeUncleanAccounts: removing call logs for account=" + account);
329             try {
330                 // The device ID for call logs is the name of the account
331                 getContentResolver()
332                         .delete(
333                                 CallLog.Calls.CONTENT_URI,
334                                 CallLog.Calls.PHONE_ACCOUNT_ID + "=?",
335                                 new String[] {account.name});
336             } catch (IllegalArgumentException e) {
337                 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.", e);
338             }
339 
340             Log.i(TAG, "removeUncleanAccounts: removing account=" + account);
341             mPbapClientAccountManager.removeAccount(account);
342         }
343     }
344 
removeHfpCallLog(String accountName)345     private void removeHfpCallLog(String accountName) {
346         Log.d(TAG, "Removing call logs from " + accountName);
347         // Delete call logs belonging to accountName==BD_ADDR that also match component "hfpclient"
348         ComponentName componentName = new ComponentName(this, HfpClientConnectionService.class);
349         String selectionFilter =
350                 CallLog.Calls.PHONE_ACCOUNT_ID
351                         + "=? AND "
352                         + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME
353                         + "=?";
354         String[] selectionArgs = new String[] {accountName, componentName.flattenToString()};
355         try {
356             getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
357         } catch (IllegalArgumentException e) {
358             Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
359         }
360     }
361 
362     /**
363      * Ensure that after HFP disconnects, we remove call logs. This addresses the situation when
364      * PBAP was never connected while calls were made. Ideally {@link PbapClientConnectionHandler}
365      * has code to remove call logs when PBAP disconnects.
366      */
handleHeadsetClientConnectionStateChanged( BluetoothDevice device, int oldState, int newState)367     public void handleHeadsetClientConnectionStateChanged(
368             BluetoothDevice device, int oldState, int newState) {
369         if (newState == STATE_DISCONNECTED) {
370             Log.d(TAG, "Received intent to disconnect HFP with " + device);
371             if (Flags.pbapClientStorageRefactor()) {
372                 Account account = mPbapClientContactsStorage.getStorageAccountForDevice(device);
373                 mPbapClientContactsStorage.removeCallHistory(account);
374                 return;
375             } else {
376                 // HFP client stores entries in calllog.db by BD_ADDR and component name
377                 // Using the current Service as the context.
378                 removeHfpCallLog(device.getAddress());
379             }
380         }
381     }
382 
383     /**
384      * Get debug information about this PbapClientService instance
385      *
386      * @param sb The StringBuilder instance to add our debug dump info to
387      */
388     @Override
dump(StringBuilder sb)389     public void dump(StringBuilder sb) {
390         super.dump(sb);
391 
392         if (Flags.pbapClientStorageRefactor()) {
393             synchronized (mPbapClientStateMachineMap) {
394                 ProfileService.println(
395                         sb,
396                         "Devices ("
397                                 + mPbapClientStateMachineMap.size()
398                                 + "/ "
399                                 + MAXIMUM_DEVICES
400                                 + ")");
401                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
402                     stateMachine.dump(sb);
403                     ProfileService.println(sb, "");
404                 }
405             }
406             ProfileService.println(sb, mPbapClientContactsStorage.dump());
407         } else {
408             for (PbapClientStateMachineOld smOld : mPbapClientStateMachineOldMap.values()) {
409                 smOld.dump(sb);
410             }
411             ProfileService.println(sb, mPbapClientAccountManager.dump());
412         }
413     }
414 
415     // *********************************************************************************************
416     // * Events from AdapterService
417     // *********************************************************************************************
418 
419     /**
420      * Get notified of incoming ACL disconnections
421      *
422      * <p>OBEX client's are supposed to be in control of the connection lifecycle, and servers are
423      * not supposed to disconnect OBEX sessions. Despite this, its normal/possible the remote device
424      * to tear down connections at lower levels than OBEX, mainly the L2CAP/RFCOMM links or the ACL.
425      * The OBEX framework isn't setup to be notified of these disconnections, so we must listen for
426      * them separately and clean up the device connection and, if necessary, data when this happens.
427      *
428      * @param device The device that had the ACL disconnect
429      * @param transport The transport the device disconnected on
430      */
aclDisconnected(BluetoothDevice device, int transport)431     public void aclDisconnected(BluetoothDevice device, int transport) {
432         mHandler.post(() -> handleAclDisconnected(device, transport));
433     }
434 
handleAclDisconnected(BluetoothDevice device, int transport)435     private void handleAclDisconnected(BluetoothDevice device, int transport) {
436         Log.i(
437                 TAG,
438                 "Received ACL disconnection event, device="
439                         + device.toString()
440                         + ", transport="
441                         + transport);
442 
443         if (transport != BluetoothDevice.TRANSPORT_BREDR) {
444             return;
445         }
446 
447         if (getConnectionState(device) == STATE_CONNECTED) {
448             disconnect(device);
449         }
450     }
451 
452     /**
453      * Get notified of incoming SDP records
454      *
455      * <p>This function looks for PBAP Server records coming from remote devices, and forwards them
456      * to the appropriate device's state machine instance for processing. SDP records are used to
457      * determine which L2CAP/RFCOMM psm/channel to connect on, as well as which phonebooks to expect
458      */
receiveSdpSearchRecord( BluetoothDevice device, int status, Parcelable record, ParcelUuid uuid)459     public void receiveSdpSearchRecord(
460             BluetoothDevice device, int status, Parcelable record, ParcelUuid uuid) {
461         Log.v(
462                 TAG,
463                 "Received SDP record for UUID="
464                         + uuid.toString()
465                         + " (expected UUID="
466                         + BluetoothUuid.PBAP_PSE.toString()
467                         + ")");
468         if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
469             SdpPseRecord pseRecord = (SdpPseRecord) record;
470             if (pseRecord == null) {
471                 Log.w(TAG, "Received null PSE record for device=" + device);
472                 return;
473             }
474 
475             if (Flags.pbapClientStorageRefactor()) {
476                 PbapClientStateMachine stateMachine = getDeviceStateMachine(device);
477                 if (stateMachine == null) {
478                     Log.e(TAG, "No StateMachine found for the device=" + device.toString());
479                     return;
480                 }
481                 stateMachine.onSdpResultReceived(status, new PbapSdpRecord(device, pseRecord));
482             } else {
483                 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device);
484                 if (smOld == null) {
485                     Log.e(TAG, "No StateMachine found for the device=" + device.toString());
486                     return;
487                 }
488                 smOld.onSdpResultReceived(status, new PbapSdpRecord(device, pseRecord));
489             }
490         }
491     }
492 
493     // *********************************************************************************************
494     // * API methods
495     // *********************************************************************************************
496 
497     /** Get the singleton instance of PbapClientService, if one exists */
getPbapClientService()498     public static synchronized PbapClientService getPbapClientService() {
499         if (sPbapClientService == null) {
500             Log.w(TAG, "getPbapClientService(): service is null");
501             return null;
502         }
503         if (!sPbapClientService.isAvailable()) {
504             Log.w(TAG, "getPbapClientService(): service is not available");
505             return null;
506         }
507         return sPbapClientService;
508     }
509 
510     /**
511      * Set the singleton instance of PbapClientService
512      *
513      * <p>This function is meant to be used by tests only.
514      */
515     @VisibleForTesting
setPbapClientService(PbapClientService instance)516     static synchronized void setPbapClientService(PbapClientService instance) {
517         Log.v(TAG, "setPbapClientService(): set to: " + instance);
518         sPbapClientService = instance;
519     }
520 
521     /**
522      * Requests a connection to the given device's PBAP Server
523      *
524      * @param device is the device with which we will connect to
525      * @return true if we successfully begin the connection process, false otherwise
526      */
connect(BluetoothDevice device)527     public boolean connect(BluetoothDevice device) {
528         if (device == null) {
529             throw new IllegalArgumentException("Null device");
530         }
531         Log.d(TAG, "connect(device=" + device.getAddress() + ")");
532         if (getConnectionPolicy(device) <= CONNECTION_POLICY_FORBIDDEN) {
533             return false;
534         }
535 
536         if (Flags.pbapClientStorageRefactor()) {
537             return addDevice(device);
538         } else {
539             synchronized (mPbapClientStateMachineOldMap) {
540                 PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device);
541                 if (smOld == null && mPbapClientStateMachineOldMap.size() < MAXIMUM_DEVICES) {
542                     HandlerThread smThread = new HandlerThread("PbapClientStateMachineOld");
543                     smThread.start();
544 
545                     smOld = new PbapClientStateMachineOld(this, device, smThread);
546                     smOld.start();
547                     mPbapClientStateMachineOldMap.put(device, smOld);
548                     return true;
549                 } else {
550                     Log.w(TAG, "Received connect request while already connecting/connected.");
551                     return false;
552                 }
553             }
554         }
555     }
556 
557     /**
558      * Disconnects the pbap client profile from the passed in device
559      *
560      * @param device is the device with which we will disconnect the pbap client profile
561      * @return true if we disconnected the pbap client profile, false otherwise
562      */
disconnect(BluetoothDevice device)563     public boolean disconnect(BluetoothDevice device) {
564         if (device == null) {
565             throw new IllegalArgumentException("Null device");
566         }
567 
568         Log.d(TAG, "disconnect(device=" + device.getAddress() + ")");
569         if (Flags.pbapClientStorageRefactor()) {
570             PbapClientStateMachine pbapClientStateMachine = getDeviceStateMachine(device);
571             if (pbapClientStateMachine != null) {
572                 pbapClientStateMachine.disconnect();
573                 return true;
574             }
575         } else {
576             PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device);
577             if (smOld != null) {
578                 smOld.disconnect(device);
579                 return true;
580             }
581         }
582 
583         Log.w(TAG, "disconnect() called on unconnected device.");
584         return false;
585     }
586 
587     /**
588      * Get the list of PBAP Server devices this PBAP Client device is connected to
589      *
590      * @return The list of connected PBAP Server devices
591      */
getConnectedDevices()592     public List<BluetoothDevice> getConnectedDevices() {
593         int[] desiredStates = {STATE_CONNECTED};
594         return getDevicesMatchingConnectionStates(desiredStates);
595     }
596 
597     /**
598      * Get the list of PBAP Server devices this PBAP Client device know about, who are in a given
599      * state.
600      *
601      * @param states The array of BluetoothProfile states you want to match on
602      * @return The list of connected PBAP Server devices
603      */
getDevicesMatchingConnectionStates(int[] states)604     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
605         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0);
606 
607         if (Flags.pbapClientStorageRefactor()) {
608             synchronized (mPbapClientStateMachineMap) {
609                 for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry :
610                         mPbapClientStateMachineMap.entrySet()) {
611                     int currentDeviceState = stateMachineEntry.getValue().getConnectionState();
612                     for (int state : states) {
613                         if (currentDeviceState == state) {
614                             deviceList.add(stateMachineEntry.getKey());
615                             break;
616                         }
617                     }
618                 }
619             }
620         } else {
621             for (Map.Entry<BluetoothDevice, PbapClientStateMachineOld> stateMachineEntryOld :
622                     mPbapClientStateMachineOldMap.entrySet()) {
623                 int currentDeviceState = stateMachineEntryOld.getValue().getConnectionState();
624                 for (int state : states) {
625                     if (currentDeviceState == state) {
626                         deviceList.add(stateMachineEntryOld.getKey());
627                         break;
628                     }
629                 }
630             }
631         }
632 
633         return deviceList;
634     }
635 
636     /**
637      * Get the current connection state of the profile
638      *
639      * @param device is the remote bluetooth device
640      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, {@link
641      *     BluetoothProfile#STATE_CONNECTING} if this profile is being connected, {@link
642      *     BluetoothProfile#STATE_CONNECTED} if this profile is connected, or {@link
643      *     BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
644      */
getConnectionState(BluetoothDevice device)645     public int getConnectionState(BluetoothDevice device) {
646         if (device == null) {
647             throw new IllegalArgumentException("Null device");
648         }
649 
650         if (Flags.pbapClientStorageRefactor()) {
651             PbapClientStateMachine pbapClientStateMachine = getDeviceStateMachine(device);
652             if (pbapClientStateMachine == null) {
653                 return STATE_DISCONNECTED;
654             } else {
655                 return pbapClientStateMachine.getConnectionState();
656             }
657         } else {
658             PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device);
659             if (smOld == null) {
660                 return STATE_DISCONNECTED;
661             } else {
662                 return smOld.getConnectionState(device);
663             }
664         }
665     }
666 
667     /**
668      * Set connection policy of the profile and connects it if connectionPolicy is {@link
669      * BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is {@link
670      * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
671      *
672      * <p>The device should already be paired. Connection policy can be one of: {@link
673      * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link
674      * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
675      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
676      *
677      * @param device Paired bluetooth device
678      * @param connectionPolicy is the connection policy to set to for this profile
679      * @return true if connectionPolicy is set, false on error
680      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)681     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
682         if (device == null) {
683             throw new IllegalArgumentException("Null device");
684         }
685         Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
686 
687         if (!mDatabaseManager.setProfileConnectionPolicy(
688                 device, BluetoothProfile.PBAP_CLIENT, connectionPolicy)) {
689             return false;
690         }
691         if (connectionPolicy == CONNECTION_POLICY_ALLOWED) {
692             connect(device);
693         } else if (connectionPolicy == CONNECTION_POLICY_FORBIDDEN) {
694             disconnect(device);
695         }
696         return true;
697     }
698 
699     /**
700      * Get the connection policy of the profile.
701      *
702      * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
703      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link
704      * BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
705      *
706      * @param device Bluetooth device
707      * @return connection policy of the device
708      */
getConnectionPolicy(BluetoothDevice device)709     public int getConnectionPolicy(BluetoothDevice device) {
710         if (device == null) {
711             throw new IllegalArgumentException("Null device");
712         }
713         return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT);
714     }
715 
716     // *********************************************************************************************
717     // * Pre-Refactor Methods
718     // *********************************************************************************************
719 
720     @VisibleForTesting
PbapClientService(AdapterService adapterService, PbapClientAccountManager accountManager)721     PbapClientService(AdapterService adapterService, PbapClientAccountManager accountManager) {
722         super(requireNonNull(adapterService));
723         mDatabaseManager = requireNonNull(adapterService.getDatabase());
724         mHandler = new Handler(Looper.getMainLooper());
725 
726         if (Flags.pbapClientStorageRefactor()) {
727             throw new IllegalStateException("pbapClientStorageRefactor: Invalid constructor call");
728         }
729 
730         mPbapClientAccountManager = requireNonNull(accountManager);
731         mPbapClientContactsStorage = null;
732         mPbapClientStateMachineMap = null;
733 
734         setComponentAvailable(AUTHENTICATOR_SERVICE, true);
735 
736         mPbapClientAccountManager.start();
737 
738         registerSdpRecord();
739         setPbapClientService(this);
740     }
741 
cleanupDevice(BluetoothDevice device)742     void cleanupDevice(BluetoothDevice device) {
743         if (Flags.pbapClientStorageRefactor()) {
744             Log.w(TAG, "This should not be used in this configuration");
745         }
746 
747         Log.d(TAG, "Cleanup device: " + device);
748         synchronized (mPbapClientStateMachineOldMap) {
749             PbapClientStateMachineOld smOld = mPbapClientStateMachineOldMap.get(device);
750             if (smOld != null) {
751                 mPbapClientStateMachineOldMap.remove(device);
752                 smOld.doQuit();
753             }
754         }
755     }
756 
757     /**
758      * Determine if our account type is available and ready to be interacted with
759      *
760      * @return True is account type is ready, false otherwise
761      */
isAccountTypeReady()762     public boolean isAccountTypeReady() {
763         if (Flags.pbapClientStorageRefactor()) {
764             Log.w(TAG, "This should not be used in this configuration");
765         }
766         return mPbapClientAccountManager.isAccountTypeInitialized();
767     }
768 
769     /**
770      * Add an account
771      *
772      * @param account The account you wish to add
773      * @return True if the account addition was successful, False otherwise
774      */
addAccount(Account account)775     public boolean addAccount(Account account) {
776         if (Flags.pbapClientStorageRefactor()) {
777             Log.w(TAG, "This should not be used in this configuration");
778         }
779         return mPbapClientAccountManager.addAccount(account);
780     }
781 
782     /**
783      * Remove an account
784      *
785      * @param account The account you wish to remove
786      * @return True if the account removal was successful, False otherwise
787      */
removeAccount(Account account)788     public boolean removeAccount(Account account) {
789         if (Flags.pbapClientStorageRefactor()) {
790             Log.w(TAG, "This should not be used in this configuration");
791         }
792         return mPbapClientAccountManager.removeAccount(account);
793     }
794 }
795