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