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