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