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.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothHeadsetClient;
23 import android.bluetooth.BluetoothProfile;
24 import android.bluetooth.IBluetoothPbapClient;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.provider.CallLog;
31 import android.util.Log;
32 
33 import com.android.bluetooth.R;
34 import com.android.bluetooth.btservice.AdapterService;
35 import com.android.bluetooth.btservice.ProfileService;
36 import com.android.bluetooth.btservice.storage.DatabaseManager;
37 import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
38 import com.android.bluetooth.sdp.SdpManager;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 import java.util.concurrent.ConcurrentHashMap;
45 
46 /**
47  * Provides Bluetooth Phone Book Access Profile Client profile.
48  *
49  * @hide
50  */
51 public class PbapClientService extends ProfileService {
52     private static final boolean DBG = Utils.DBG;
53     private static final boolean VDBG = Utils.VDBG;
54 
55     private static final String TAG = "PbapClientService";
56     private static final String SERVICE_NAME = "Phonebook Access PCE";
57     // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
58     private static final int MAXIMUM_DEVICES = 10;
59     private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap =
60             new ConcurrentHashMap<>();
61     private static PbapClientService sPbapClientService;
62     private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
63     private int mSdpHandle = -1;
64 
65     private DatabaseManager mDatabaseManager;
66 
67     @Override
initBinder()68     public IProfileServiceBinder initBinder() {
69         return new BluetoothPbapClientBinder(this);
70     }
71 
72     @Override
start()73     protected boolean start() {
74         if (VDBG) {
75             Log.v(TAG, "onStart");
76         }
77 
78         mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
79                 "DatabaseManager cannot be null when PbapClientService starts");
80 
81         IntentFilter filter = new IntentFilter();
82         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
83         // delay initial download until after the user is unlocked to add an account.
84         filter.addAction(Intent.ACTION_USER_UNLOCKED);
85         // To remove call logs when PBAP was never connected while calls were made,
86         // we also listen for HFP to become disconnected.
87         filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
88         try {
89             registerReceiver(mPbapBroadcastReceiver, filter);
90         } catch (Exception e) {
91             Log.w(TAG, "Unable to register pbapclient receiver", e);
92         }
93 
94         removeUncleanAccounts();
95         registerSdpRecord();
96         setPbapClientService(this);
97         return true;
98     }
99 
100     @Override
stop()101     protected boolean stop() {
102         setPbapClientService(null);
103         cleanUpSdpRecord();
104         try {
105             unregisterReceiver(mPbapBroadcastReceiver);
106         } catch (Exception e) {
107             Log.w(TAG, "Unable to unregister pbapclient receiver", e);
108         }
109         for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) {
110             pbapClientStateMachine.doQuit();
111         }
112         removeUncleanAccounts();
113         return true;
114     }
115 
cleanupDevice(BluetoothDevice device)116     void cleanupDevice(BluetoothDevice device) {
117         if (DBG) Log.d(TAG, "Cleanup device: " + device);
118         synchronized (mPbapClientStateMachineMap) {
119             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
120             if (pbapClientStateMachine != null) {
121                 mPbapClientStateMachineMap.remove(device);
122             }
123         }
124     }
125 
removeUncleanAccounts()126     private void removeUncleanAccounts() {
127         // Find all accounts that match the type "pbap" and delete them.
128         AccountManager accountManager = AccountManager.get(this);
129         Account[] accounts =
130                 accountManager.getAccountsByType(getString(R.string.pbap_account_type));
131         if (VDBG) Log.v(TAG, "Found " + accounts.length + " unclean accounts");
132         for (Account acc : accounts) {
133             Log.w(TAG, "Deleting " + acc);
134             try {
135                 getContentResolver().delete(CallLog.Calls.CONTENT_URI,
136                         CallLog.Calls.PHONE_ACCOUNT_ID + "=?", new String[]{acc.name});
137             } catch (IllegalArgumentException e) {
138                 Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
139             }
140             // The device ID is the name of the account.
141             accountManager.removeAccountExplicitly(acc);
142         }
143     }
144 
removeHfpCallLog(String accountName, Context context)145     private void removeHfpCallLog(String accountName, Context context) {
146         if (DBG) Log.d(TAG, "Removing call logs from " + accountName);
147         // Delete call logs belonging to accountName==BD_ADDR that also match
148         // component name "hfpclient".
149         ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
150         String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND "
151                 + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?";
152         String[] selectionArgs = new String[]{accountName, componentName.flattenToString()};
153         try {
154             getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
155         } catch (IllegalArgumentException e) {
156             Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
157         }
158     }
159 
registerSdpRecord()160     private void registerSdpRecord() {
161         SdpManager sdpManager = SdpManager.getDefaultManager();
162         if (sdpManager == null) {
163             Log.e(TAG, "SdpManager is null");
164             return;
165         }
166         mSdpHandle = sdpManager.createPbapPceRecord(SERVICE_NAME,
167                 PbapClientConnectionHandler.PBAP_V1_2);
168     }
169 
cleanUpSdpRecord()170     private void cleanUpSdpRecord() {
171         if (mSdpHandle < 0) {
172             Log.e(TAG, "cleanUpSdpRecord, SDP record never created");
173             return;
174         }
175         int sdpHandle = mSdpHandle;
176         mSdpHandle = -1;
177         SdpManager sdpManager = SdpManager.getDefaultManager();
178         if (sdpManager == null) {
179             Log.e(TAG, "cleanUpSdpRecord failed, sdpManager is null, sdpHandle=" + sdpHandle);
180             return;
181         }
182         Log.i(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
183         if (!sdpManager.removeSdpRecord(sdpHandle)) {
184             Log.e(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
185         }
186     }
187 
188 
189     private class PbapBroadcastReceiver extends BroadcastReceiver {
190         @Override
onReceive(Context context, Intent intent)191         public void onReceive(Context context, Intent intent) {
192             String action = intent.getAction();
193             if (DBG) Log.v(TAG, "onReceive" + action);
194             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
195                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
196                 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
197                     disconnect(device);
198                 }
199             } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
200                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
201                     stateMachine.resumeDownload();
202                 }
203             } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) {
204                 // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects.
205                 // However, if PBAP was never connected/enabled in the first place, and calls are
206                 // made over HFP, these calllogs will not be removed when the device disconnects.
207                 // This code ensures callogs are still removed in this case.
208                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
209                 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
210 
211                 if (newState == BluetoothProfile.STATE_DISCONNECTED) {
212                     if (DBG) {
213                         Log.d(TAG, "Received intent to disconnect HFP with " + device);
214                     }
215                     // HFP client stores entries in calllog.db by BD_ADDR and component name
216                     removeHfpCallLog(device.getAddress(), context);
217                 }
218             }
219         }
220     }
221 
222     /**
223      * Handler for incoming service calls
224      */
225     private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub
226             implements IProfileServiceBinder {
227         private PbapClientService mService;
228 
BluetoothPbapClientBinder(PbapClientService svc)229         BluetoothPbapClientBinder(PbapClientService svc) {
230             mService = svc;
231         }
232 
233         @Override
cleanup()234         public void cleanup() {
235             mService = null;
236         }
237 
getService()238         private PbapClientService getService() {
239             if (!com.android.bluetooth.Utils.checkCaller()) {
240                 Log.w(TAG, "PbapClient call not allowed for non-active user");
241                 return null;
242             }
243 
244             if (mService != null && mService.isAvailable()) {
245                 return mService;
246             }
247             return null;
248         }
249 
250         @Override
connect(BluetoothDevice device)251         public boolean connect(BluetoothDevice device) {
252             PbapClientService service = getService();
253             if (DBG) {
254                 Log.d(TAG, "PbapClient Binder connect ");
255             }
256             if (service == null) {
257                 Log.e(TAG, "PbapClient Binder connect no service");
258                 return false;
259             }
260             return service.connect(device);
261         }
262 
263         @Override
disconnect(BluetoothDevice device)264         public boolean disconnect(BluetoothDevice device) {
265             PbapClientService service = getService();
266             if (service == null) {
267                 return false;
268             }
269             return service.disconnect(device);
270         }
271 
272         @Override
getConnectedDevices()273         public List<BluetoothDevice> getConnectedDevices() {
274             PbapClientService service = getService();
275             if (service == null) {
276                 return new ArrayList<BluetoothDevice>(0);
277             }
278             return service.getConnectedDevices();
279         }
280 
281         @Override
getDevicesMatchingConnectionStates(int[] states)282         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
283             PbapClientService service = getService();
284             if (service == null) {
285                 return new ArrayList<BluetoothDevice>(0);
286             }
287             return service.getDevicesMatchingConnectionStates(states);
288         }
289 
290         @Override
getConnectionState(BluetoothDevice device)291         public int getConnectionState(BluetoothDevice device) {
292             PbapClientService service = getService();
293             if (service == null) {
294                 return BluetoothProfile.STATE_DISCONNECTED;
295             }
296             return service.getConnectionState(device);
297         }
298 
299         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)300         public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
301             PbapClientService service = getService();
302             if (service == null) {
303                 return false;
304             }
305             return service.setConnectionPolicy(device, connectionPolicy);
306         }
307 
308         @Override
getConnectionPolicy(BluetoothDevice device)309         public int getConnectionPolicy(BluetoothDevice device) {
310             PbapClientService service = getService();
311             if (service == null) {
312                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
313             }
314             return service.getConnectionPolicy(device);
315         }
316 
317 
318     }
319 
320     // API methods
getPbapClientService()321     public static synchronized PbapClientService getPbapClientService() {
322         if (sPbapClientService == null) {
323             Log.w(TAG, "getPbapClientService(): service is null");
324             return null;
325         }
326         if (!sPbapClientService.isAvailable()) {
327             Log.w(TAG, "getPbapClientService(): service is not available");
328             return null;
329         }
330         return sPbapClientService;
331     }
332 
setPbapClientService(PbapClientService instance)333     private static synchronized void setPbapClientService(PbapClientService instance) {
334         if (VDBG) {
335             Log.v(TAG, "setPbapClientService(): set to: " + instance);
336         }
337         sPbapClientService = instance;
338     }
339 
connect(BluetoothDevice device)340     public boolean connect(BluetoothDevice device) {
341         if (device == null) {
342             throw new IllegalArgumentException("Null device");
343         }
344         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
345                 "Need BLUETOOTH_PRIVILEGED permission");
346         if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
347         if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
348             return false;
349         }
350         synchronized (mPbapClientStateMachineMap) {
351             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
352             if (pbapClientStateMachine == null
353                     && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) {
354                 pbapClientStateMachine = new PbapClientStateMachine(this, device);
355                 pbapClientStateMachine.start();
356                 mPbapClientStateMachineMap.put(device, pbapClientStateMachine);
357                 return true;
358             } else {
359                 Log.w(TAG, "Received connect request while already connecting/connected.");
360                 return false;
361             }
362         }
363     }
364 
365     /**
366      * Disconnects the pbap client profile from the passed in device
367      *
368      * @param device is the device with which we will disconnect the pbap client profile
369      * @return true if we disconnected the pbap client profile, false otherwise
370      */
disconnect(BluetoothDevice device)371     public boolean disconnect(BluetoothDevice device) {
372         if (device == null) {
373             throw new IllegalArgumentException("Null device");
374         }
375         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
376                 "Need BLUETOOTH_PRIVILEGED permission");
377         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
378         if (pbapClientStateMachine != null) {
379             pbapClientStateMachine.disconnect(device);
380             return true;
381 
382         } else {
383             Log.w(TAG, "disconnect() called on unconnected device.");
384             return false;
385         }
386     }
387 
getConnectedDevices()388     public List<BluetoothDevice> getConnectedDevices() {
389         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
390         int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
391         return getDevicesMatchingConnectionStates(desiredStates);
392     }
393 
getDevicesMatchingConnectionStates(int[] states)394     private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
395         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
396         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0);
397         for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry :
398                 mPbapClientStateMachineMap
399                 .entrySet()) {
400             int currentDeviceState = stateMachineEntry.getValue().getConnectionState();
401             for (int state : states) {
402                 if (currentDeviceState == state) {
403                     deviceList.add(stateMachineEntry.getKey());
404                     break;
405                 }
406             }
407         }
408         return deviceList;
409     }
410 
411     /**
412      * Get the current connection state of the profile
413      *
414      * @param device is the remote bluetooth device
415      * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
416      * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
417      * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
418      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
419      */
getConnectionState(BluetoothDevice device)420     public int getConnectionState(BluetoothDevice device) {
421         if (device == null) {
422             throw new IllegalArgumentException("Null device");
423         }
424         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
425         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
426         if (pbapClientStateMachine == null) {
427             return BluetoothProfile.STATE_DISCONNECTED;
428         } else {
429             return pbapClientStateMachine.getConnectionState(device);
430         }
431     }
432 
433     /**
434      * Set connection policy of the profile and connects it if connectionPolicy is
435      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
436      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
437      *
438      * <p> The device should already be paired.
439      * Connection policy can be one of:
440      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
441      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
442      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
443      *
444      * @param device Paired bluetooth device
445      * @param connectionPolicy is the connection policy to set to for this profile
446      * @return true if connectionPolicy is set, false on error
447      */
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)448     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
449         if (device == null) {
450             throw new IllegalArgumentException("Null device");
451         }
452         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
453                 "Need BLUETOOTH_PRIVILEGED permission");
454         if (DBG) {
455             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
456         }
457 
458         if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT,
459                   connectionPolicy)) {
460             return false;
461         }
462         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
463             connect(device);
464         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
465             disconnect(device);
466         }
467         return true;
468     }
469 
470     /**
471      * Get the connection policy of the profile.
472      *
473      * <p> The connection policy can be any of:
474      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
475      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
476      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
477      *
478      * @param device Bluetooth device
479      * @return connection policy of the device
480      * @hide
481      */
getConnectionPolicy(BluetoothDevice device)482     public int getConnectionPolicy(BluetoothDevice device) {
483         if (device == null) {
484             throw new IllegalArgumentException("Null device");
485         }
486         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
487                 "Need BLUETOOTH_PRIVILEGED permission");
488         return mDatabaseManager
489                 .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT);
490     }
491 
492     @Override
dump(StringBuilder sb)493     public void dump(StringBuilder sb) {
494         super.dump(sb);
495         for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
496             stateMachine.dump(sb);
497         }
498     }
499 }
500