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