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