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