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.BluetoothProfile;
23 import android.bluetooth.IBluetoothPbapClient;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.provider.CallLog;
29 import android.provider.Settings;
30 import android.util.Log;
31 
32 import com.android.bluetooth.btservice.ProfileService;
33 import com.android.bluetooth.R;
34 import com.android.bluetooth.Utils;
35 
36 import java.lang.IllegalArgumentException;
37 import java.util.ArrayList;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.List;
40 import java.util.Map;
41 
42 /**
43  * Provides Bluetooth Phone Book Access Profile Client profile.
44  *
45  * @hide
46  */
47 public class PbapClientService extends ProfileService {
48     private static final boolean DBG = false;
49     private static final String TAG = "PbapClientService";
50     // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices.
51     private static final int MAXIMUM_DEVICES = 10;
52     private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap =
53             new ConcurrentHashMap<>();
54     private static PbapClientService sPbapClientService;
55     private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
56 
57     @Override
getName()58     protected String getName() {
59         return TAG;
60     }
61 
62     @Override
initBinder()63     public IProfileServiceBinder initBinder() {
64         return new BluetoothPbapClientBinder(this);
65     }
66 
67     @Override
start()68     protected boolean start() {
69         if (DBG) Log.d(TAG, "onStart");
70         IntentFilter filter = new IntentFilter();
71         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
72         // delay initial download until after the user is unlocked to add an account.
73         filter.addAction(Intent.ACTION_USER_UNLOCKED);
74         try {
75             registerReceiver(mPbapBroadcastReceiver, filter);
76         } catch (Exception e) {
77             Log.w(TAG,"Unable to register pbapclient receiver", e);
78         }
79         removeUncleanAccounts();
80         setPbapClientService(this);
81         return true;
82     }
83 
84     @Override
stop()85     protected boolean stop() {
86         try {
87             unregisterReceiver(mPbapBroadcastReceiver);
88         } catch (Exception e) {
89             Log.w(TAG,"Unable to unregister pbapclient receiver", e);
90         }
91         for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) {
92             pbapClientStateMachine.doQuit();
93         }
94         return true;
95     }
96 
97     @Override
cleanup()98     protected boolean cleanup() {
99         removeUncleanAccounts();
100         clearPbapClientService();
101         return true;
102     }
103 
cleanupDevice(BluetoothDevice device)104     void cleanupDevice(BluetoothDevice device) {
105         Log.w(TAG, "Cleanup device: " + device);
106         synchronized (mPbapClientStateMachineMap) {
107             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
108             if (pbapClientStateMachine != null) {
109                 mPbapClientStateMachineMap.remove(device);
110             }
111         }
112     }
113 
removeUncleanAccounts()114     private void removeUncleanAccounts() {
115         // Find all accounts that match the type "pbap" and delete them.
116         AccountManager accountManager = AccountManager.get(this);
117         Account[] accounts =
118                 accountManager.getAccountsByType(getString(R.string.pbap_account_type));
119         Log.w(TAG, "Found " + accounts.length + " unclean accounts");
120         for (Account acc : accounts) {
121             Log.w(TAG, "Deleting " + acc);
122             // The device ID is the name of the account.
123             accountManager.removeAccountExplicitly(acc);
124         }
125         try {
126             getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null);
127         } catch (IllegalArgumentException e) {
128             Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
129         }
130     }
131 
132     private class PbapBroadcastReceiver extends BroadcastReceiver {
133         @Override
onReceive(Context context, Intent intent)134         public void onReceive(Context context, Intent intent) {
135             String action = intent.getAction();
136             Log.v(TAG, "onReceive" + action);
137             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
138                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
139                 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
140                     disconnect(device);
141                 }
142             } else if(action.equals(Intent.ACTION_USER_UNLOCKED)) {
143                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
144                     stateMachine.resumeDownload();
145                 }
146             }
147         }
148     }
149 
150     /**
151      * Handler for incoming service calls
152      */
153     private static class BluetoothPbapClientBinder extends IBluetoothPbapClient.Stub
154             implements IProfileServiceBinder {
155         private PbapClientService mService;
156 
BluetoothPbapClientBinder(PbapClientService svc)157         public BluetoothPbapClientBinder(PbapClientService svc) {
158             mService = svc;
159         }
160 
161         @Override
cleanup()162         public boolean cleanup() {
163             mService = null;
164             return true;
165         }
166 
getService()167         private PbapClientService getService() {
168             if (!Utils.checkCaller()) {
169                 Log.w(TAG, "PbapClient call not allowed for non-active user");
170                 return null;
171             }
172 
173             if (mService != null && mService.isAvailable()) {
174                 return mService;
175             }
176             return null;
177         }
178 
179         @Override
connect(BluetoothDevice device)180         public boolean connect(BluetoothDevice device) {
181             PbapClientService service = getService();
182             if (DBG) Log.d(TAG, "PbapClient Binder connect " );
183             if (service == null) {
184                 Log.e(TAG, "PbapClient Binder connect no service");
185                 return false;
186             }
187             return service.connect(device);
188         }
189 
190         @Override
disconnect(BluetoothDevice device)191         public boolean disconnect(BluetoothDevice device) {
192             PbapClientService service = getService();
193             if (service == null) {
194                 return false;
195             }
196             return service.disconnect(device);
197         }
198 
199         @Override
getConnectedDevices()200         public List<BluetoothDevice> getConnectedDevices() {
201             PbapClientService service = getService();
202             if (service == null) {
203                 return new ArrayList<BluetoothDevice>(0);
204             }
205             return service.getConnectedDevices();
206         }
207         @Override
getDevicesMatchingConnectionStates(int[] states)208         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
209             PbapClientService service = getService();
210             if (service == null) {
211                 return new ArrayList<BluetoothDevice>(0);
212             }
213             return service.getDevicesMatchingConnectionStates(states);
214         }
215 
216         @Override
getConnectionState(BluetoothDevice device)217         public int getConnectionState(BluetoothDevice device) {
218             PbapClientService service = getService();
219             if (service == null) {
220                 return BluetoothProfile.STATE_DISCONNECTED;
221             }
222             return service.getConnectionState(device);
223         }
224 
225         @Override
setPriority(BluetoothDevice device, int priority)226         public boolean setPriority(BluetoothDevice device, int priority) {
227             PbapClientService service = getService();
228             if (service == null) {
229                 return false;
230             }
231             return service.setPriority(device, priority);
232         }
233 
234         @Override
getPriority(BluetoothDevice device)235         public int getPriority(BluetoothDevice device) {
236             PbapClientService service = getService();
237             if (service == null) {
238                 return BluetoothProfile.PRIORITY_UNDEFINED;
239             }
240             return service.getPriority(device);
241         }
242 
243 
244     }
245 
246     // API methods
getPbapClientService()247     public static synchronized PbapClientService getPbapClientService() {
248         if (sPbapClientService != null && sPbapClientService.isAvailable()) {
249             if (DBG) {
250                 Log.d(TAG, "getPbapClientService(): returning " + sPbapClientService);
251             }
252             return sPbapClientService;
253         }
254         if (DBG) {
255             if (sPbapClientService == null) {
256                 Log.d(TAG, "getPbapClientService(): service is NULL");
257             } else if (!(sPbapClientService.isAvailable())) {
258                 Log.d(TAG, "getPbapClientService(): service is not available");
259             }
260         }
261         return null;
262     }
263 
setPbapClientService(PbapClientService instance)264     private static synchronized void setPbapClientService(PbapClientService instance) {
265         if (instance != null && instance.isAvailable()) {
266             if (DBG) {
267                 Log.d(TAG, "setPbapClientService(): previously set to: " + sPbapClientService);
268             }
269             sPbapClientService = instance;
270         } else {
271             if (DBG) {
272                 if (sPbapClientService == null) {
273                     Log.d(TAG, "setPbapClientService(): service not available");
274                 } else if (!sPbapClientService.isAvailable()) {
275                     Log.d(TAG, "setPbapClientService(): service is cleaning up");
276                 }
277             }
278         }
279     }
280 
clearPbapClientService()281     private static synchronized void clearPbapClientService() {
282         sPbapClientService = null;
283     }
284 
connect(BluetoothDevice device)285     public boolean connect(BluetoothDevice device) {
286         if (device == null) throw new IllegalArgumentException("Null device");
287         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
288         Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress());
289         if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
290             return false;
291         }
292         synchronized (mPbapClientStateMachineMap) {
293             PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
294             if (pbapClientStateMachine == null
295                     && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) {
296                 pbapClientStateMachine = new PbapClientStateMachine(this, device);
297                 pbapClientStateMachine.start();
298                 mPbapClientStateMachineMap.put(device, pbapClientStateMachine);
299                 return true;
300             } else {
301                 Log.w(TAG, "Received connect request while already connecting/connected.");
302                 return false;
303             }
304         }
305     }
306 
disconnect(BluetoothDevice device)307     boolean disconnect(BluetoothDevice device) {
308         if (device == null) throw new IllegalArgumentException("Null device");
309         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
310         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
311         if (pbapClientStateMachine != null) {
312             pbapClientStateMachine.disconnect(device);
313             return true;
314 
315         } else {
316             Log.w(TAG, "disconnect() called on unconnected device.");
317             return false;
318         }
319     }
320 
getConnectedDevices()321     public List<BluetoothDevice> getConnectedDevices() {
322         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
323         int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
324         return getDevicesMatchingConnectionStates(desiredStates);
325     }
326 
getDevicesMatchingConnectionStates(int[] states)327     private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
328         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
329         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0);
330         for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry :
331                 mPbapClientStateMachineMap.entrySet()) {
332             int currentDeviceState = stateMachineEntry.getValue().getConnectionState();
333             for (int state : states) {
334                 if (currentDeviceState == state) {
335                     deviceList.add(stateMachineEntry.getKey());
336                     break;
337                 }
338             }
339         }
340         return deviceList;
341     }
342 
getConnectionState(BluetoothDevice device)343     int getConnectionState(BluetoothDevice device) {
344         if (device == null) throw new IllegalArgumentException("Null device");
345         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
346         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
347         if (pbapClientStateMachine == null) {
348             return BluetoothProfile.STATE_DISCONNECTED;
349         } else {
350             return pbapClientStateMachine.getConnectionState(device);
351         }
352     }
353 
setPriority(BluetoothDevice device, int priority)354     public boolean setPriority(BluetoothDevice device, int priority) {
355         if (device == null) throw new IllegalArgumentException("Null device");
356         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
357         Settings.Global.putInt(getContentResolver(),
358                 Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
359                 priority);
360         if (DBG) {
361             Log.d(TAG,"Saved priority " + device + " = " + priority);
362         }
363         return true;
364     }
365 
getPriority(BluetoothDevice device)366     public int getPriority(BluetoothDevice device) {
367         if (device == null) throw new IllegalArgumentException("Null device");
368         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
369         int priority = Settings.Global.getInt(getContentResolver(),
370                 Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
371                 BluetoothProfile.PRIORITY_UNDEFINED);
372         return priority;
373     }
374 
375     @Override
dump(StringBuilder sb)376     public void dump(StringBuilder sb) {
377         super.dump(sb);
378         for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
379             stateMachine.dump(sb);
380         }
381     }
382 }
383