1 /* 2 * Copyright (c) 2014 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.hfpclient; 18 19 import static android.content.pm.PackageManager.FEATURE_WATCH; 20 21 import android.annotation.RequiresPermission; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadsetClient; 24 import android.bluetooth.BluetoothHeadsetClientCall; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothSinkAudioPolicy; 27 import android.bluetooth.BluetoothStatusCodes; 28 import android.bluetooth.IBluetoothHeadsetClient; 29 import android.content.AttributionSource; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.media.AudioManager; 35 import android.os.BatteryManager; 36 import android.os.Bundle; 37 import android.os.HandlerThread; 38 import android.os.Message; 39 import android.os.SystemProperties; 40 import android.sysprop.BluetoothProperties; 41 import android.util.Log; 42 43 import com.android.bluetooth.Utils; 44 import com.android.bluetooth.btservice.AdapterService; 45 import com.android.bluetooth.btservice.ProfileService; 46 import com.android.bluetooth.btservice.storage.DatabaseManager; 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.HashMap; 53 import java.util.Iterator; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Objects; 57 import java.util.Set; 58 import java.util.UUID; 59 60 /** 61 * Provides Bluetooth Headset Client (HF Role) profile, as a service in the Bluetooth application. 62 */ 63 public class HeadsetClientService extends ProfileService { 64 private static final String TAG = HeadsetClientService.class.getSimpleName(); 65 66 // This is also used as a lock for shared data in {@link HeadsetClientService} 67 @GuardedBy("mStateMachineMap") 68 private final HashMap<BluetoothDevice, HeadsetClientStateMachine> mStateMachineMap = 69 new HashMap<>(); 70 71 private static HeadsetClientService sHeadsetClientService; 72 private NativeInterface mNativeInterface = null; 73 private HandlerThread mSmThread = null; 74 private HeadsetClientStateMachineFactory mSmFactory = null; 75 private DatabaseManager mDatabaseManager; 76 private AudioManager mAudioManager = null; 77 private BatteryManager mBatteryManager = null; 78 private int mLastBatteryLevel = -1; 79 // Maxinum number of devices we can try connecting to in one session 80 private static final int MAX_STATE_MACHINES_POSSIBLE = 100; 81 82 private final Object mStartStopLock = new Object(); 83 84 public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag"; 85 HeadsetClientService(Context ctx)86 public HeadsetClientService(Context ctx) { 87 super(ctx); 88 } 89 isEnabled()90 public static boolean isEnabled() { 91 return BluetoothProperties.isProfileHfpHfEnabled().orElse(false); 92 } 93 94 @Override initBinder()95 public IProfileServiceBinder initBinder() { 96 return new BluetoothHeadsetClientBinder(this); 97 } 98 99 @Override start()100 public void start() { 101 synchronized (mStartStopLock) { 102 Log.d(TAG, "start()"); 103 if (getHeadsetClientService() != null) { 104 throw new IllegalStateException("start() called twice"); 105 } 106 107 mDatabaseManager = 108 Objects.requireNonNull( 109 AdapterService.getAdapterService().getDatabase(), 110 "DatabaseManager cannot be null when HeadsetClientService starts"); 111 112 // Setup the JNI service 113 mNativeInterface = NativeInterface.getInstance(); 114 mNativeInterface.initialize(); 115 116 mBatteryManager = getSystemService(BatteryManager.class); 117 118 mAudioManager = getSystemService(AudioManager.class); 119 if (mAudioManager == null) { 120 Log.e(TAG, "AudioManager service doesn't exist?"); 121 } else { 122 // start AudioManager in a known state 123 mAudioManager.setHfpEnabled(false); 124 } 125 126 mSmFactory = new HeadsetClientStateMachineFactory(); 127 synchronized (mStateMachineMap) { 128 mStateMachineMap.clear(); 129 } 130 131 IntentFilter filter = new IntentFilter(AudioManager.ACTION_VOLUME_CHANGED); 132 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 133 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 134 registerReceiver(mBroadcastReceiver, filter); 135 136 // Start the HfpClientConnectionService to create connection with telecom when HFP 137 // connection is available on non-wearable device. 138 if (getPackageManager() != null 139 && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) { 140 Intent startIntent = new Intent(this, HfpClientConnectionService.class); 141 startService(startIntent); 142 } 143 144 // Create the thread on which all State Machines will run 145 mSmThread = new HandlerThread("HeadsetClient.SM"); 146 mSmThread.start(); 147 148 setHeadsetClientService(this); 149 } 150 } 151 152 @Override stop()153 public void stop() { 154 synchronized (mStartStopLock) { 155 synchronized (HeadsetClientService.class) { 156 if (sHeadsetClientService == null) { 157 Log.w(TAG, "stop() called without start()"); 158 return; 159 } 160 161 // Stop the HfpClientConnectionService for non-wearables devices. 162 if (getPackageManager() != null 163 && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) { 164 Intent stopIntent = new Intent(this, HfpClientConnectionService.class); 165 sHeadsetClientService.stopService(stopIntent); 166 } 167 } 168 169 setHeadsetClientService(null); 170 171 unregisterReceiver(mBroadcastReceiver); 172 173 synchronized (mStateMachineMap) { 174 for (Iterator<Map.Entry<BluetoothDevice, HeadsetClientStateMachine>> it = 175 mStateMachineMap.entrySet().iterator(); 176 it.hasNext(); ) { 177 HeadsetClientStateMachine sm = 178 mStateMachineMap.get((BluetoothDevice) it.next().getKey()); 179 sm.doQuit(); 180 it.remove(); 181 } 182 } 183 184 // Stop the handler thread 185 mSmThread.quit(); 186 mSmThread = null; 187 188 mNativeInterface.cleanup(); 189 mNativeInterface = null; 190 } 191 } 192 193 private final BroadcastReceiver mBroadcastReceiver = 194 new BroadcastReceiver() { 195 @Override 196 public void onReceive(Context context, Intent intent) { 197 String action = intent.getAction(); 198 199 // We handle the volume changes for Voice calls here since HFP audio volume 200 // control does 201 // not go through audio manager (audio mixer). see 202 // ({@link HeadsetClientStateMachine#SET_SPEAKER_VOLUME} in 203 // {@link HeadsetClientStateMachine} for details. 204 if (action.equals(AudioManager.ACTION_VOLUME_CHANGED)) { 205 Log.d( 206 TAG, 207 "Volume changed for stream: " 208 + intent.getIntExtra( 209 AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1)); 210 int streamType = 211 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 212 if (streamType == AudioManager.STREAM_VOICE_CALL) { 213 int streamValue = 214 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); 215 int hfVol = HeadsetClientStateMachine.amToHfVol(streamValue); 216 Log.d( 217 TAG, 218 "Setting volume to audio manager: " 219 + streamValue 220 + " hands free: " 221 + hfVol); 222 mAudioManager.setHfpVolume(hfVol); 223 synchronized (mStateMachineMap) { 224 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 225 if (sm != null) { 226 sm.sendMessage( 227 HeadsetClientStateMachine.SET_SPEAKER_VOLUME, 228 streamValue); 229 } 230 } 231 } 232 } 233 } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 234 int batteryIndicatorID = 2; 235 int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); 236 237 if (batteryLevel == mLastBatteryLevel) { 238 return; 239 } 240 mLastBatteryLevel = batteryLevel; 241 242 Log.d( 243 TAG, 244 "Send battery level update BIEV(2," + batteryLevel + ") command"); 245 246 synchronized (mStateMachineMap) { 247 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 248 if (sm != null) { 249 sm.sendMessage( 250 HeadsetClientStateMachine.SEND_BIEV, 251 batteryIndicatorID, 252 batteryLevel); 253 } 254 } 255 } 256 } 257 } 258 }; 259 260 /** 261 * Convert {@code HfpClientCall} to legacy {@code BluetoothHeadsetClientCall} still used by some 262 * clients. 263 */ toLegacyCall(HfpClientCall call)264 static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { 265 if (call == null) return null; 266 return new BluetoothHeadsetClientCall( 267 call.getDevice(), 268 call.getId(), 269 call.getUUID(), 270 call.getState(), 271 call.getNumber(), 272 call.isMultiParty(), 273 call.isOutgoing(), 274 call.isInBandRing()); 275 } 276 277 /** Handlers for incoming service calls */ 278 @VisibleForTesting 279 static class BluetoothHeadsetClientBinder extends IBluetoothHeadsetClient.Stub 280 implements IProfileServiceBinder { 281 private HeadsetClientService mService; 282 BluetoothHeadsetClientBinder(HeadsetClientService svc)283 BluetoothHeadsetClientBinder(HeadsetClientService svc) { 284 mService = svc; 285 } 286 287 @Override cleanup()288 public void cleanup() { 289 mService = null; 290 } 291 292 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)293 private HeadsetClientService getService(AttributionSource source) { 294 if (Utils.isInstrumentationTestMode()) { 295 return mService; 296 } 297 if (!Utils.checkServiceAvailable(mService, TAG) 298 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 299 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 300 return null; 301 } 302 return mService; 303 } 304 305 @Override connect(BluetoothDevice device, AttributionSource source)306 public boolean connect(BluetoothDevice device, AttributionSource source) { 307 HeadsetClientService service = getService(source); 308 if (service == null) { 309 return false; 310 } 311 312 return service.connect(device); 313 } 314 315 @Override disconnect(BluetoothDevice device, AttributionSource source)316 public boolean disconnect(BluetoothDevice device, AttributionSource source) { 317 HeadsetClientService service = getService(source); 318 if (service == null) { 319 return false; 320 } 321 322 return service.disconnect(device); 323 } 324 325 @Override getConnectedDevices(AttributionSource source)326 public List<BluetoothDevice> getConnectedDevices(AttributionSource source) { 327 HeadsetClientService service = getService(source); 328 if (service == null) { 329 return Collections.emptyList(); 330 } 331 332 return service.getConnectedDevices(); 333 } 334 335 @Override getDevicesMatchingConnectionStates( int[] states, AttributionSource source)336 public List<BluetoothDevice> getDevicesMatchingConnectionStates( 337 int[] states, AttributionSource source) { 338 HeadsetClientService service = getService(source); 339 if (service == null) { 340 return Collections.emptyList(); 341 } 342 343 return service.getDevicesMatchingConnectionStates(states); 344 } 345 346 @Override getConnectionState(BluetoothDevice device, AttributionSource source)347 public int getConnectionState(BluetoothDevice device, AttributionSource source) { 348 HeadsetClientService service = getService(source); 349 if (service == null) { 350 return BluetoothProfile.STATE_DISCONNECTED; 351 } 352 353 return service.getConnectionState(device); 354 } 355 356 @Override setConnectionPolicy( BluetoothDevice device, int connectionPolicy, AttributionSource source)357 public boolean setConnectionPolicy( 358 BluetoothDevice device, int connectionPolicy, AttributionSource source) { 359 HeadsetClientService service = getService(source); 360 if (service == null) { 361 return false; 362 } 363 364 return service.setConnectionPolicy(device, connectionPolicy); 365 } 366 367 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source)368 public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) { 369 HeadsetClientService service = getService(source); 370 if (service == null) { 371 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 372 } 373 374 return service.getConnectionPolicy(device); 375 } 376 377 @Override startVoiceRecognition(BluetoothDevice device, AttributionSource source)378 public boolean startVoiceRecognition(BluetoothDevice device, AttributionSource source) { 379 HeadsetClientService service = getService(source); 380 if (service == null) { 381 return false; 382 } 383 384 return service.startVoiceRecognition(device); 385 } 386 387 @Override stopVoiceRecognition(BluetoothDevice device, AttributionSource source)388 public boolean stopVoiceRecognition(BluetoothDevice device, AttributionSource source) { 389 HeadsetClientService service = getService(source); 390 if (service == null) { 391 return false; 392 } 393 394 return service.stopVoiceRecognition(device); 395 } 396 397 @Override getAudioState(BluetoothDevice device, AttributionSource source)398 public int getAudioState(BluetoothDevice device, AttributionSource source) { 399 HeadsetClientService service = getService(source); 400 if (service == null) { 401 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; 402 } 403 404 return service.getAudioState(device); 405 } 406 407 @Override setAudioRouteAllowed( BluetoothDevice device, boolean allowed, AttributionSource source)408 public void setAudioRouteAllowed( 409 BluetoothDevice device, boolean allowed, AttributionSource source) { 410 HeadsetClientService service = getService(source); 411 if (service == null) { 412 Log.w(TAG, "Service handle is null for setAudioRouteAllowed!"); 413 return; 414 } 415 416 service.setAudioRouteAllowed(device, allowed); 417 } 418 419 @Override getAudioRouteAllowed(BluetoothDevice device, AttributionSource source)420 public boolean getAudioRouteAllowed(BluetoothDevice device, AttributionSource source) { 421 HeadsetClientService service = getService(source); 422 if (service == null) { 423 Log.w(TAG, "Service handle is null for getAudioRouteAllowed!"); 424 return false; 425 } 426 427 return service.getAudioRouteAllowed(device); 428 } 429 430 @Override connectAudio(BluetoothDevice device, AttributionSource source)431 public boolean connectAudio(BluetoothDevice device, AttributionSource source) { 432 HeadsetClientService service = getService(source); 433 if (service == null) { 434 return false; 435 } 436 437 return service.connectAudio(device); 438 } 439 440 @Override disconnectAudio(BluetoothDevice device, AttributionSource source)441 public boolean disconnectAudio(BluetoothDevice device, AttributionSource source) { 442 HeadsetClientService service = getService(source); 443 if (service == null) { 444 return false; 445 } 446 447 return service.disconnectAudio(device); 448 } 449 450 @Override acceptCall(BluetoothDevice device, int flag, AttributionSource source)451 public boolean acceptCall(BluetoothDevice device, int flag, AttributionSource source) { 452 HeadsetClientService service = getService(source); 453 if (service == null) { 454 return false; 455 } 456 457 return service.acceptCall(device, flag); 458 } 459 460 @Override rejectCall(BluetoothDevice device, AttributionSource source)461 public boolean rejectCall(BluetoothDevice device, AttributionSource source) { 462 HeadsetClientService service = getService(source); 463 if (service == null) { 464 return false; 465 } 466 467 return service.rejectCall(device); 468 } 469 470 @Override holdCall(BluetoothDevice device, AttributionSource source)471 public boolean holdCall(BluetoothDevice device, AttributionSource source) { 472 HeadsetClientService service = getService(source); 473 if (service == null) { 474 return false; 475 } 476 477 return service.holdCall(device); 478 } 479 480 @Override terminateCall( BluetoothDevice device, BluetoothHeadsetClientCall call, AttributionSource source)481 public boolean terminateCall( 482 BluetoothDevice device, BluetoothHeadsetClientCall call, AttributionSource source) { 483 HeadsetClientService service = getService(source); 484 if (service == null) { 485 Log.w(TAG, "service is null"); 486 return false; 487 } 488 489 return service.terminateCall(device, call != null ? call.getUUID() : null); 490 } 491 492 @Override explicitCallTransfer(BluetoothDevice device, AttributionSource source)493 public boolean explicitCallTransfer(BluetoothDevice device, AttributionSource source) { 494 HeadsetClientService service = getService(source); 495 if (service == null) { 496 return false; 497 } 498 499 return service.explicitCallTransfer(device); 500 } 501 502 @Override enterPrivateMode( BluetoothDevice device, int index, AttributionSource source)503 public boolean enterPrivateMode( 504 BluetoothDevice device, int index, AttributionSource source) { 505 HeadsetClientService service = getService(source); 506 if (service == null) { 507 return false; 508 } 509 510 return service.enterPrivateMode(device, index); 511 } 512 513 @Override dial( BluetoothDevice device, String number, AttributionSource source)514 public BluetoothHeadsetClientCall dial( 515 BluetoothDevice device, String number, AttributionSource source) { 516 HeadsetClientService service = getService(source); 517 if (service == null) { 518 return null; 519 } 520 521 return toLegacyCall(service.dial(device, number)); 522 } 523 524 @Override getCurrentCalls( BluetoothDevice device, AttributionSource source)525 public List<BluetoothHeadsetClientCall> getCurrentCalls( 526 BluetoothDevice device, AttributionSource source) { 527 HeadsetClientService service = getService(source); 528 List<BluetoothHeadsetClientCall> currentCalls = new ArrayList<>(); 529 if (service == null) { 530 return currentCalls; 531 } 532 533 List<HfpClientCall> calls = service.getCurrentCalls(device); 534 if (calls != null) { 535 for (HfpClientCall call : calls) { 536 currentCalls.add(toLegacyCall(call)); 537 } 538 } 539 return currentCalls; 540 } 541 542 @Override sendDTMF(BluetoothDevice device, byte code, AttributionSource source)543 public boolean sendDTMF(BluetoothDevice device, byte code, AttributionSource source) { 544 HeadsetClientService service = getService(source); 545 if (service == null) { 546 return false; 547 } 548 549 return service.sendDTMF(device, code); 550 } 551 552 @Override getLastVoiceTagNumber(BluetoothDevice device, AttributionSource source)553 public boolean getLastVoiceTagNumber(BluetoothDevice device, AttributionSource source) { 554 HeadsetClientService service = getService(source); 555 if (service == null) { 556 return false; 557 } 558 559 return service.getLastVoiceTagNumber(device); 560 } 561 562 @Override getCurrentAgEvents(BluetoothDevice device, AttributionSource source)563 public Bundle getCurrentAgEvents(BluetoothDevice device, AttributionSource source) { 564 HeadsetClientService service = getService(source); 565 if (service == null) { 566 return null; 567 } 568 569 return service.getCurrentAgEvents(device); 570 } 571 572 @Override sendVendorAtCommand( BluetoothDevice device, int vendorId, String atCommand, AttributionSource source)573 public boolean sendVendorAtCommand( 574 BluetoothDevice device, int vendorId, String atCommand, AttributionSource source) { 575 HeadsetClientService service = getService(source); 576 if (service == null) { 577 return false; 578 } 579 580 return service.sendVendorAtCommand(device, vendorId, atCommand); 581 } 582 583 @Override getCurrentAgFeatures(BluetoothDevice device, AttributionSource source)584 public Bundle getCurrentAgFeatures(BluetoothDevice device, AttributionSource source) { 585 HeadsetClientService service = getService(source); 586 if (service == null) { 587 return null; 588 } 589 590 return service.getCurrentAgFeaturesBundle(device); 591 } 592 } 593 594 // API methods getHeadsetClientService()595 public static synchronized HeadsetClientService getHeadsetClientService() { 596 if (sHeadsetClientService == null) { 597 Log.w(TAG, "getHeadsetClientService(): service is null"); 598 return null; 599 } 600 if (!sHeadsetClientService.isAvailable()) { 601 Log.w(TAG, "getHeadsetClientService(): service is not available "); 602 return null; 603 } 604 return sHeadsetClientService; 605 } 606 607 /** Set a {@link HeadsetClientService} instance. */ 608 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) setHeadsetClientService(HeadsetClientService instance)609 public static synchronized void setHeadsetClientService(HeadsetClientService instance) { 610 Log.d(TAG, "setHeadsetClientService(): set to: " + instance); 611 sHeadsetClientService = instance; 612 } 613 connect(BluetoothDevice device)614 public boolean connect(BluetoothDevice device) { 615 Log.d(TAG, "connect " + device); 616 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 617 Log.w( 618 TAG, 619 "Connection not allowed: <" 620 + device.getAddress() 621 + "> is CONNECTION_POLICY_FORBIDDEN"); 622 return false; 623 } 624 HeadsetClientStateMachine sm = getStateMachine(device, true); 625 if (sm == null) { 626 Log.e(TAG, "Cannot allocate SM for device " + device); 627 return false; 628 } 629 630 sm.sendMessage(HeadsetClientStateMachine.CONNECT, device); 631 return true; 632 } 633 634 /** 635 * Disconnects hfp client for the remote bluetooth device 636 * 637 * @param device is the device with which we are attempting to disconnect the profile 638 * @return true if hfp client profile successfully disconnected, false otherwise 639 */ disconnect(BluetoothDevice device)640 public boolean disconnect(BluetoothDevice device) { 641 HeadsetClientStateMachine sm = getStateMachine(device); 642 if (sm == null) { 643 Log.e(TAG, "SM does not exist for device " + device); 644 return false; 645 } 646 647 int connectionState = sm.getConnectionState(device); 648 if (connectionState != BluetoothProfile.STATE_CONNECTED 649 && connectionState != BluetoothProfile.STATE_CONNECTING) { 650 return false; 651 } 652 653 sm.sendMessage(HeadsetClientStateMachine.DISCONNECT, device); 654 return true; 655 } 656 657 /** 658 * @return A list of connected {@link BluetoothDevice}. 659 */ getConnectedDevices()660 public List<BluetoothDevice> getConnectedDevices() { 661 ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); 662 synchronized (mStateMachineMap) { 663 for (BluetoothDevice bd : mStateMachineMap.keySet()) { 664 HeadsetClientStateMachine sm = mStateMachineMap.get(bd); 665 if (sm != null && sm.getConnectionState(bd) == BluetoothProfile.STATE_CONNECTED) { 666 connectedDevices.add(bd); 667 } 668 } 669 } 670 return connectedDevices; 671 } 672 getDevicesMatchingConnectionStates(int[] states)673 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 674 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 675 synchronized (mStateMachineMap) { 676 for (BluetoothDevice bd : mStateMachineMap.keySet()) { 677 for (int state : states) { 678 HeadsetClientStateMachine sm = mStateMachineMap.get(bd); 679 if (sm != null && sm.getConnectionState(bd) == state) { 680 devices.add(bd); 681 } 682 } 683 } 684 } 685 return devices; 686 } 687 688 /** 689 * Get the current connection state of the profile 690 * 691 * @param device is the remote bluetooth device 692 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, {@link 693 * BluetoothProfile#STATE_CONNECTING} if this profile is being connected, {@link 694 * BluetoothProfile#STATE_CONNECTED} if this profile is connected, or {@link 695 * BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 696 */ getConnectionState(BluetoothDevice device)697 public int getConnectionState(BluetoothDevice device) { 698 HeadsetClientStateMachine sm = getStateMachine(device); 699 if (sm != null) { 700 return sm.getConnectionState(device); 701 } 702 703 return BluetoothProfile.STATE_DISCONNECTED; 704 } 705 706 /** 707 * Set connection policy of the profile and connects it if connectionPolicy is {@link 708 * BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is {@link 709 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 710 * 711 * <p>The device should already be paired. Connection policy can be one of: {@link 712 * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link 713 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 714 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 715 * 716 * @param device Paired bluetooth device 717 * @param connectionPolicy is the connection policy to set to for this profile 718 * @return true if connectionPolicy is set, false on error 719 */ setConnectionPolicy(BluetoothDevice device, int connectionPolicy)720 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 721 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 722 723 if (!mDatabaseManager.setProfileConnectionPolicy( 724 device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy)) { 725 return false; 726 } 727 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 728 connect(device); 729 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 730 disconnect(device); 731 } 732 return true; 733 } 734 735 /** 736 * Get the connection policy of the profile. 737 * 738 * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 739 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 740 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 741 * 742 * @param device Bluetooth device 743 * @return connection policy of the device 744 */ getConnectionPolicy(BluetoothDevice device)745 public int getConnectionPolicy(BluetoothDevice device) { 746 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT); 747 } 748 startVoiceRecognition(BluetoothDevice device)749 boolean startVoiceRecognition(BluetoothDevice device) { 750 HeadsetClientStateMachine sm = getStateMachine(device); 751 if (sm == null) { 752 Log.e(TAG, "SM does not exist for device " + device); 753 return false; 754 } 755 int connectionState = sm.getConnectionState(device); 756 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 757 return false; 758 } 759 sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START); 760 return true; 761 } 762 stopVoiceRecognition(BluetoothDevice device)763 boolean stopVoiceRecognition(BluetoothDevice device) { 764 HeadsetClientStateMachine sm = getStateMachine(device); 765 if (sm == null) { 766 Log.e(TAG, "SM does not exist for device " + device); 767 return false; 768 } 769 int connectionState = sm.getConnectionState(device); 770 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 771 return false; 772 } 773 sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_STOP); 774 return true; 775 } 776 777 /** 778 * Gets audio state of the connection with {@code device}. 779 * 780 * <p>Can be one of {@link STATE_AUDIO_CONNECTED}, {@link STATE_AUDIO_CONNECTING}, or {@link 781 * STATE_AUDIO_DISCONNECTED}. 782 */ getAudioState(BluetoothDevice device)783 public int getAudioState(BluetoothDevice device) { 784 HeadsetClientStateMachine sm = getStateMachine(device); 785 if (sm == null) { 786 Log.e(TAG, "SM does not exist for device " + device); 787 return -1; 788 } 789 790 return sm.getAudioState(device); 791 } 792 setAudioRouteAllowed(BluetoothDevice device, boolean allowed)793 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { 794 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 795 Log.i( 796 TAG, 797 "setAudioRouteAllowed: device=" 798 + device 799 + ", allowed=" 800 + allowed 801 + ", " 802 + Utils.getUidPidString()); 803 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 804 if (sm != null) { 805 sm.setAudioRouteAllowed(allowed); 806 } 807 } 808 getAudioRouteAllowed(BluetoothDevice device)809 public boolean getAudioRouteAllowed(BluetoothDevice device) { 810 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 811 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 812 if (sm != null) { 813 return sm.getAudioRouteAllowed(); 814 } 815 return false; 816 } 817 818 /** 819 * sends the {@link BluetoothSinkAudioPolicy} object to the state machine of the corresponding 820 * device to store and send to the remote device using Android specific AT commands. 821 * 822 * @param device for whom the policies to be set 823 * @param policies to be set policies 824 */ setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies)825 public void setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies) { 826 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 827 Log.i( 828 TAG, 829 "setAudioPolicy: device=" 830 + device 831 + ", " 832 + policies.toString() 833 + ", " 834 + Utils.getUidPidString()); 835 HeadsetClientStateMachine sm = getStateMachine(device); 836 if (sm != null) { 837 sm.setAudioPolicy(policies); 838 } 839 } 840 841 /** 842 * sets the audio policy feature support status for the corresponding device. 843 * 844 * @param device for whom the policies to be set 845 * @param supported support status 846 */ setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported)847 public void setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported) { 848 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 849 Log.i(TAG, "setAudioPolicyRemoteSupported: " + supported); 850 HeadsetClientStateMachine sm = getStateMachine(device); 851 if (sm != null) { 852 sm.setAudioPolicyRemoteSupported(supported); 853 } 854 } 855 856 /** 857 * gets the audio policy feature support status for the corresponding device. 858 * 859 * @param device for whom the policies to be set 860 * @return int support status 861 */ getAudioPolicyRemoteSupported(BluetoothDevice device)862 public int getAudioPolicyRemoteSupported(BluetoothDevice device) { 863 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 864 HeadsetClientStateMachine sm = getStateMachine(device); 865 if (sm != null) { 866 return sm.getAudioPolicyRemoteSupported(); 867 } 868 return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED; 869 } 870 connectAudio(BluetoothDevice device)871 public boolean connectAudio(BluetoothDevice device) { 872 Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString()); 873 HeadsetClientStateMachine sm = getStateMachine(device); 874 if (sm == null) { 875 Log.e(TAG, "SM does not exist for device " + device); 876 return false; 877 } 878 879 if (!sm.isConnected()) { 880 return false; 881 } 882 if (sm.isAudioOn()) { 883 return false; 884 } 885 sm.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO); 886 return true; 887 } 888 disconnectAudio(BluetoothDevice device)889 public boolean disconnectAudio(BluetoothDevice device) { 890 HeadsetClientStateMachine sm = getStateMachine(device); 891 if (sm == null) { 892 Log.e(TAG, "SM does not exist for device " + device); 893 return false; 894 } 895 896 if (!sm.isAudioOn()) { 897 return false; 898 } 899 sm.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO); 900 return true; 901 } 902 holdCall(BluetoothDevice device)903 public boolean holdCall(BluetoothDevice device) { 904 HeadsetClientStateMachine sm = getStateMachine(device); 905 if (sm == null) { 906 Log.e(TAG, "SM does not exist for device " + device); 907 return false; 908 } 909 910 int connectionState = sm.getConnectionState(device); 911 if (connectionState != BluetoothProfile.STATE_CONNECTED 912 && connectionState != BluetoothProfile.STATE_CONNECTING) { 913 return false; 914 } 915 Message msg = sm.obtainMessage(HeadsetClientStateMachine.HOLD_CALL); 916 sm.sendMessage(msg); 917 return true; 918 } 919 acceptCall(BluetoothDevice device, int flag)920 public boolean acceptCall(BluetoothDevice device, int flag) { 921 /* Phonecalls from a single device are supported, hang up any calls on the other phone */ 922 synchronized (mStateMachineMap) { 923 for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : 924 mStateMachineMap.entrySet()) { 925 if (entry.getValue() == null || entry.getKey().equals(device)) { 926 continue; 927 } 928 int connectionState = entry.getValue().getConnectionState(entry.getKey()); 929 Log.d( 930 TAG, 931 "Accepting a call on device " 932 + device 933 + ". Possibly disconnecting on " 934 + entry.getValue()); 935 if (connectionState == BluetoothProfile.STATE_CONNECTED) { 936 entry.getValue() 937 .obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL) 938 .sendToTarget(); 939 } 940 } 941 } 942 HeadsetClientStateMachine sm = getStateMachine(device); 943 if (sm == null) { 944 Log.e(TAG, "SM does not exist for device " + device); 945 return false; 946 } 947 948 int connectionState = sm.getConnectionState(device); 949 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 950 return false; 951 } 952 Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); 953 msg.arg1 = flag; 954 sm.sendMessage(msg); 955 return true; 956 } 957 rejectCall(BluetoothDevice device)958 public boolean rejectCall(BluetoothDevice device) { 959 HeadsetClientStateMachine sm = getStateMachine(device); 960 if (sm == null) { 961 Log.e(TAG, "SM does not exist for device " + device); 962 return false; 963 } 964 965 int connectionState = sm.getConnectionState(device); 966 if (connectionState != BluetoothProfile.STATE_CONNECTED 967 && connectionState != BluetoothProfile.STATE_CONNECTING) { 968 return false; 969 } 970 971 Message msg = sm.obtainMessage(HeadsetClientStateMachine.REJECT_CALL); 972 sm.sendMessage(msg); 973 return true; 974 } 975 terminateCall(BluetoothDevice device, UUID uuid)976 public boolean terminateCall(BluetoothDevice device, UUID uuid) { 977 HeadsetClientStateMachine sm = getStateMachine(device); 978 if (sm == null) { 979 Log.e(TAG, "SM does not exist for device " + device); 980 return false; 981 } 982 983 int connectionState = sm.getConnectionState(device); 984 if (connectionState != BluetoothProfile.STATE_CONNECTED 985 && connectionState != BluetoothProfile.STATE_CONNECTING) { 986 return false; 987 } 988 989 Message msg = sm.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL); 990 msg.obj = uuid; 991 sm.sendMessage(msg); 992 return true; 993 } 994 enterPrivateMode(BluetoothDevice device, int index)995 public boolean enterPrivateMode(BluetoothDevice device, int index) { 996 HeadsetClientStateMachine sm = getStateMachine(device); 997 if (sm == null) { 998 Log.e(TAG, "SM does not exist for device " + device); 999 return false; 1000 } 1001 1002 int connectionState = sm.getConnectionState(device); 1003 if (connectionState != BluetoothProfile.STATE_CONNECTED 1004 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1005 return false; 1006 } 1007 1008 Message msg = sm.obtainMessage(HeadsetClientStateMachine.ENTER_PRIVATE_MODE); 1009 msg.arg1 = index; 1010 sm.sendMessage(msg); 1011 return true; 1012 } 1013 dial(BluetoothDevice device, String number)1014 public HfpClientCall dial(BluetoothDevice device, String number) { 1015 HeadsetClientStateMachine sm = getStateMachine(device); 1016 if (sm == null) { 1017 Log.e(TAG, "SM does not exist for device " + device); 1018 return null; 1019 } 1020 1021 int connectionState = sm.getConnectionState(device); 1022 if (connectionState != BluetoothProfile.STATE_CONNECTED 1023 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1024 return null; 1025 } 1026 1027 // Some platform does not support three way calling (ex: watch) 1028 final boolean support_three_way_calling = 1029 SystemProperties.getBoolean( 1030 "bluetooth.headset_client.three_way_calling.enabled", true); 1031 if (!support_three_way_calling && !getCurrentCalls(device).isEmpty()) { 1032 Log.e(TAG, String.format("dial(%s): Line is busy, reject dialing", device)); 1033 return null; 1034 } 1035 1036 HfpClientCall call = 1037 new HfpClientCall( 1038 device, 1039 HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID, 1040 HfpClientCall.CALL_STATE_DIALING, 1041 number, 1042 false /* multiparty */, 1043 true /* outgoing */, 1044 sm.getInBandRing()); 1045 Message msg = sm.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER); 1046 msg.obj = call; 1047 sm.sendMessage(msg); 1048 return call; 1049 } 1050 sendDTMF(BluetoothDevice device, byte code)1051 public boolean sendDTMF(BluetoothDevice device, byte code) { 1052 HeadsetClientStateMachine sm = getStateMachine(device); 1053 if (sm == null) { 1054 Log.e(TAG, "SM does not exist for device " + device); 1055 return false; 1056 } 1057 1058 int connectionState = sm.getConnectionState(device); 1059 if (connectionState != BluetoothProfile.STATE_CONNECTED 1060 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1061 return false; 1062 } 1063 Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_DTMF); 1064 msg.arg1 = code; 1065 sm.sendMessage(msg); 1066 return true; 1067 } 1068 getLastVoiceTagNumber(BluetoothDevice device)1069 public boolean getLastVoiceTagNumber(BluetoothDevice device) { 1070 return false; 1071 } 1072 getCurrentCalls(BluetoothDevice device)1073 public List<HfpClientCall> getCurrentCalls(BluetoothDevice device) { 1074 HeadsetClientStateMachine sm = getStateMachine(device); 1075 if (sm == null) { 1076 Log.e(TAG, "SM does not exist for device " + device); 1077 return null; 1078 } 1079 1080 int connectionState = sm.getConnectionState(device); 1081 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1082 return null; 1083 } 1084 return sm.getCurrentCalls(); 1085 } 1086 explicitCallTransfer(BluetoothDevice device)1087 public boolean explicitCallTransfer(BluetoothDevice device) { 1088 HeadsetClientStateMachine sm = getStateMachine(device); 1089 if (sm == null) { 1090 Log.e(TAG, "SM does not exist for device " + device); 1091 return false; 1092 } 1093 1094 int connectionState = sm.getConnectionState(device); 1095 if (connectionState != BluetoothProfile.STATE_CONNECTED 1096 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1097 return false; 1098 } 1099 Message msg = sm.obtainMessage(HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER); 1100 sm.sendMessage(msg); 1101 return true; 1102 } 1103 1104 /** Send vendor AT command. */ sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)1105 public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { 1106 HeadsetClientStateMachine sm = getStateMachine(device); 1107 if (sm == null) { 1108 Log.e(TAG, "SM does not exist for device " + device); 1109 return false; 1110 } 1111 1112 int connectionState = sm.getConnectionState(device); 1113 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1114 return false; 1115 } 1116 1117 Message msg = 1118 sm.obtainMessage( 1119 HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand); 1120 sm.sendMessage(msg); 1121 return true; 1122 } 1123 getCurrentAgEvents(BluetoothDevice device)1124 public Bundle getCurrentAgEvents(BluetoothDevice device) { 1125 HeadsetClientStateMachine sm = getStateMachine(device); 1126 if (sm == null) { 1127 Log.e(TAG, "SM does not exist for device " + device); 1128 return null; 1129 } 1130 1131 int connectionState = sm.getConnectionState(device); 1132 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1133 return null; 1134 } 1135 return sm.getCurrentAgEvents(); 1136 } 1137 getCurrentAgFeaturesBundle(BluetoothDevice device)1138 public Bundle getCurrentAgFeaturesBundle(BluetoothDevice device) { 1139 HeadsetClientStateMachine sm = getStateMachine(device); 1140 if (sm == null) { 1141 Log.e(TAG, "SM does not exist for device " + device); 1142 return null; 1143 } 1144 int connectionState = sm.getConnectionState(device); 1145 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1146 return null; 1147 } 1148 return sm.getCurrentAgFeaturesBundle(); 1149 } 1150 getCurrentAgFeatures(BluetoothDevice device)1151 public Set<Integer> getCurrentAgFeatures(BluetoothDevice device) { 1152 HeadsetClientStateMachine sm = getStateMachine(device); 1153 if (sm == null) { 1154 Log.e(TAG, "SM does not exist for device " + device); 1155 return null; 1156 } 1157 int connectionState = sm.getConnectionState(device); 1158 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1159 return null; 1160 } 1161 return sm.getCurrentAgFeatures(); 1162 } 1163 1164 // Handle messages from native (JNI) to java messageFromNative(StackEvent stackEvent)1165 public void messageFromNative(StackEvent stackEvent) { 1166 Objects.requireNonNull( 1167 stackEvent.device, "Device should never be null, event: " + stackEvent); 1168 1169 HeadsetClientStateMachine sm = 1170 getStateMachine(stackEvent.device, isConnectionEvent(stackEvent)); 1171 if (sm == null) { 1172 throw new IllegalStateException( 1173 "State machine not found for stack event: " + stackEvent); 1174 } 1175 sm.sendMessage(StackEvent.STACK_EVENT, stackEvent); 1176 } 1177 isConnectionEvent(StackEvent stackEvent)1178 private boolean isConnectionEvent(StackEvent stackEvent) { 1179 if (stackEvent.type == StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 1180 if ((stackEvent.valueInt == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING) 1181 || (stackEvent.valueInt 1182 == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED)) { 1183 return true; 1184 } 1185 } 1186 return false; 1187 } 1188 getStateMachine(BluetoothDevice device)1189 private HeadsetClientStateMachine getStateMachine(BluetoothDevice device) { 1190 return getStateMachine(device, false); 1191 } 1192 getStateMachine( BluetoothDevice device, boolean isConnectionEvent)1193 private HeadsetClientStateMachine getStateMachine( 1194 BluetoothDevice device, boolean isConnectionEvent) { 1195 if (device == null) { 1196 Log.e(TAG, "getStateMachine failed: Device cannot be null"); 1197 return null; 1198 } 1199 1200 HeadsetClientStateMachine sm; 1201 synchronized (mStateMachineMap) { 1202 sm = mStateMachineMap.get(device); 1203 } 1204 1205 if (sm != null) { 1206 Log.d(TAG, "Found SM for device " + device); 1207 } else if (isConnectionEvent) { 1208 // The only time a new state machine should be created when none was found is for 1209 // connection events. 1210 sm = allocateStateMachine(device); 1211 if (sm == null) { 1212 Log.e(TAG, "SM could not be allocated for device " + device); 1213 } 1214 } 1215 return sm; 1216 } 1217 allocateStateMachine(BluetoothDevice device)1218 private HeadsetClientStateMachine allocateStateMachine(BluetoothDevice device) { 1219 if (device == null) { 1220 Log.e(TAG, "allocateStateMachine failed: Device cannot be null"); 1221 return null; 1222 } 1223 1224 if (getHeadsetClientService() == null) { 1225 // Preconditions: {@code setHeadsetClientService(this)} is the last thing {@code start} 1226 // does, and {@code setHeadsetClientService(null)} is (one of) the first thing 1227 // {@code stop does}. 1228 Log.e( 1229 TAG, 1230 "Cannot allocate SM if service has begun stopping or has not completed" 1231 + " startup."); 1232 return null; 1233 } 1234 1235 synchronized (mStateMachineMap) { 1236 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 1237 if (sm != null) { 1238 Log.d(TAG, "allocateStateMachine: SM already exists for device " + device); 1239 return sm; 1240 } 1241 1242 // There is a possibility of a DOS attack if someone populates here with a lot of fake 1243 // BluetoothAddresses. If it so happens instead of blowing up we can at least put a 1244 // limit on how long the attack would survive 1245 if (mStateMachineMap.keySet().size() > MAX_STATE_MACHINES_POSSIBLE) { 1246 Log.e( 1247 TAG, 1248 "Max state machines reached, possible DOS attack " 1249 + MAX_STATE_MACHINES_POSSIBLE); 1250 return null; 1251 } 1252 1253 // Allocate a new SM 1254 Log.d(TAG, "Creating a new state machine"); 1255 sm = mSmFactory.make(this, mSmThread, mNativeInterface); 1256 mStateMachineMap.put(device, sm); 1257 return sm; 1258 } 1259 } 1260 1261 // Check if any of the state machines have routed the SCO audio stream. isScoRouted()1262 boolean isScoRouted() { 1263 synchronized (mStateMachineMap) { 1264 for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : 1265 mStateMachineMap.entrySet()) { 1266 if (entry.getValue() != null) { 1267 int audioState = entry.getValue().getAudioState(entry.getKey()); 1268 if (audioState == HeadsetClientHalConstants.AUDIO_STATE_CONNECTED) { 1269 Log.d( 1270 TAG, 1271 "Device " 1272 + entry.getKey() 1273 + " audio state " 1274 + audioState 1275 + " Connected"); 1276 return true; 1277 } 1278 } 1279 } 1280 } 1281 return false; 1282 } 1283 handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel)1284 void handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel) { 1285 AdapterService.getAdapterService() 1286 .getRemoteDevices() 1287 .handleAgBatteryLevelChanged(device, batteryLevel); 1288 } 1289 1290 @Override dump(StringBuilder sb)1291 public void dump(StringBuilder sb) { 1292 super.dump(sb); 1293 synchronized (mStateMachineMap) { 1294 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 1295 if (sm != null) { 1296 sm.dump(sb); 1297 } 1298 } 1299 1300 sb.append("\n"); 1301 HfpClientConnectionService.dump(sb); 1302 } 1303 } 1304 1305 // For testing getStateMachineMap()1306 protected Map<BluetoothDevice, HeadsetClientStateMachine> getStateMachineMap() { 1307 synchronized (mStateMachineMap) { 1308 return mStateMachineMap; 1309 } 1310 } 1311 setSMFactory(HeadsetClientStateMachineFactory factory)1312 protected void setSMFactory(HeadsetClientStateMachineFactory factory) { 1313 mSmFactory = factory; 1314 } 1315 getAudioManager()1316 protected AudioManager getAudioManager() { 1317 return mAudioManager; 1318 } 1319 updateBatteryLevel()1320 protected void updateBatteryLevel() { 1321 int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 1322 int batteryIndicatorID = 2; 1323 1324 synchronized (mStateMachineMap) { 1325 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 1326 if (sm != null) { 1327 sm.sendMessage( 1328 HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); 1329 } 1330 } 1331 } 1332 } 1333 } 1334