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.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; 20 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 21 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; 23 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 24 import static android.content.pm.PackageManager.FEATURE_WATCH; 25 26 import static java.util.Objects.requireNonNull; 27 28 import android.bluetooth.BluetoothDevice; 29 import android.bluetooth.BluetoothHeadsetClientCall; 30 import android.bluetooth.BluetoothProfile; 31 import android.bluetooth.BluetoothSinkAudioPolicy; 32 import android.bluetooth.BluetoothStatusCodes; 33 import android.content.BroadcastReceiver; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.media.AudioManager; 38 import android.os.BatteryManager; 39 import android.os.Bundle; 40 import android.os.HandlerThread; 41 import android.os.Message; 42 import android.os.SystemProperties; 43 import android.sysprop.BluetoothProperties; 44 import android.util.Log; 45 46 import com.android.bluetooth.Utils; 47 import com.android.bluetooth.btservice.AdapterService; 48 import com.android.bluetooth.btservice.ProfileService; 49 import com.android.bluetooth.btservice.storage.DatabaseManager; 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.util.ArrayList; 54 import java.util.HashMap; 55 import java.util.List; 56 import java.util.Map; 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 // Maximum number of devices we can try connecting to in one session 67 private static final int MAX_STATE_MACHINES_POSSIBLE = 100; 68 69 @VisibleForTesting static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec. 70 @VisibleForTesting static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec. 71 72 // This is also used as a lock for shared data in {@link HeadsetClientService} 73 @GuardedBy("mStateMachineMap") 74 private final HashMap<BluetoothDevice, HeadsetClientStateMachine> mStateMachineMap = 75 new HashMap<>(); 76 77 private static HeadsetClientService sHeadsetClientService; 78 79 private final HandlerThread mSmThread; 80 private final AdapterService mAdapterService; 81 private final DatabaseManager mDatabaseManager; 82 private final AudioManager mAudioManager; 83 private final NativeInterface mNativeInterface; 84 private final BatteryManager mBatteryManager; 85 private final HeadsetClientStateMachineFactory mSmFactory; 86 private final int mMaxAmVcVol; 87 private final int mMinAmVcVol; 88 89 private int mLastBatteryLevel = -1; 90 91 public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag"; 92 HeadsetClientService(AdapterService adapterService)93 public HeadsetClientService(AdapterService adapterService) { 94 super(requireNonNull(adapterService)); 95 mAdapterService = adapterService; 96 mDatabaseManager = requireNonNull(adapterService.getDatabase()); 97 mAudioManager = requireNonNull(getSystemService(AudioManager.class)); 98 mMaxAmVcVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL); 99 mMinAmVcVol = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL); 100 101 // Setup the JNI service 102 mNativeInterface = NativeInterface.getInstance(); 103 mNativeInterface.initialize(); 104 105 mBatteryManager = getSystemService(BatteryManager.class); 106 107 // start AudioManager in a known state 108 mAudioManager.setHfpEnabled(false); 109 110 mSmFactory = new HeadsetClientStateMachineFactory(); 111 synchronized (mStateMachineMap) { 112 mStateMachineMap.clear(); 113 } 114 115 IntentFilter filter = new IntentFilter(AudioManager.ACTION_VOLUME_CHANGED); 116 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 117 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 118 registerReceiver(mBroadcastReceiver, filter); 119 120 // Start the HfpClientConnectionService to create connection with telecom when HFP 121 // connection is available on non-wearable device. 122 if (getPackageManager() != null && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) { 123 Intent startIntent = new Intent(this, HfpClientConnectionService.class); 124 startService(startIntent); 125 } 126 127 // Create the thread on which all State Machines will run 128 mSmThread = new HandlerThread("HeadsetClient.SM"); 129 mSmThread.start(); 130 131 setHeadsetClientService(this); 132 } 133 isEnabled()134 public static boolean isEnabled() { 135 return BluetoothProperties.isProfileHfpHfEnabled().orElse(false); 136 } 137 138 @Override initBinder()139 public IProfileServiceBinder initBinder() { 140 return new HeadsetClientServiceBinder(this); 141 } 142 143 @Override cleanup()144 public void cleanup() { 145 Log.i(TAG, "Cleanup Headset Client Service"); 146 147 synchronized (HeadsetClientService.class) { 148 if (sHeadsetClientService == null) { 149 Log.w(TAG, "cleanup() called before initialization"); 150 return; 151 } 152 153 // Stop the HfpClientConnectionService for non-wearables devices. 154 if (getPackageManager() != null 155 && !getPackageManager().hasSystemFeature(FEATURE_WATCH)) { 156 Intent stopIntent = new Intent(this, HfpClientConnectionService.class); 157 sHeadsetClientService.stopService(stopIntent); 158 } 159 } 160 161 setHeadsetClientService(null); 162 163 unregisterReceiver(mBroadcastReceiver); 164 165 synchronized (mStateMachineMap) { 166 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 167 sm.doQuit(); 168 } 169 mStateMachineMap.clear(); 170 } 171 172 // Stop the handler thread 173 mSmThread.quit(); 174 175 mNativeInterface.cleanup(); 176 } 177 hfToAmVol(int hfVol)178 int hfToAmVol(int hfVol) { 179 int amRange = mMaxAmVcVol - mMinAmVcVol; 180 int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; 181 int amVol = 182 (int) 183 Math.round( 184 (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME) 185 * ((double) amRange / hfRange)) 186 + mMinAmVcVol; 187 Log.d(TAG, "HF -> AM " + hfVol + " " + amVol); 188 return amVol; 189 } 190 amToHfVol(int amVol)191 int amToHfVol(int amVol) { 192 int amRange = (mMaxAmVcVol > mMinAmVcVol) ? (mMaxAmVcVol - mMinAmVcVol) : 1; 193 int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; 194 int hfVol = 195 (int) Math.round((amVol - mMinAmVcVol) * ((double) hfRange / amRange)) 196 + MIN_HFP_SCO_VOICE_CALL_VOLUME; 197 Log.d(TAG, "AM -> HF " + amVol + " " + hfVol); 198 return hfVol; 199 } 200 201 private final BroadcastReceiver mBroadcastReceiver = 202 new BroadcastReceiver() { 203 @Override 204 public void onReceive(Context context, Intent intent) { 205 String action = intent.getAction(); 206 207 // We handle the volume changes for Voice calls here since HFP audio volume 208 // control does 209 // not go through audio manager (audio mixer). see 210 // ({@link HeadsetClientStateMachine#SET_SPEAKER_VOLUME} in 211 // {@link HeadsetClientStateMachine} for details. 212 if (action.equals(AudioManager.ACTION_VOLUME_CHANGED)) { 213 Log.d( 214 TAG, 215 "Volume changed for stream: " 216 + intent.getIntExtra( 217 AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1)); 218 int streamType = 219 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 220 if (streamType == AudioManager.STREAM_VOICE_CALL) { 221 int streamValue = 222 intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); 223 int hfVol = amToHfVol(streamValue); 224 Log.d( 225 TAG, 226 "Setting volume to audio manager: " 227 + streamValue 228 + " hands free: " 229 + hfVol); 230 mAudioManager.setHfpVolume(hfVol); 231 synchronized (mStateMachineMap) { 232 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 233 if (sm != null) { 234 sm.sendMessage( 235 HeadsetClientStateMachine.SET_SPEAKER_VOLUME, 236 streamValue); 237 } 238 } 239 } 240 } 241 } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 242 int batteryIndicatorID = 2; 243 int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); 244 245 if (batteryLevel == mLastBatteryLevel) { 246 return; 247 } 248 mLastBatteryLevel = batteryLevel; 249 250 Log.d( 251 TAG, 252 "Send battery level update BIEV(2," + batteryLevel + ") command"); 253 254 synchronized (mStateMachineMap) { 255 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 256 if (sm != null) { 257 sm.sendMessage( 258 HeadsetClientStateMachine.SEND_BIEV, 259 batteryIndicatorID, 260 batteryLevel); 261 } 262 } 263 } 264 } 265 } 266 }; 267 268 /** 269 * Convert {@code HfpClientCall} to legacy {@code BluetoothHeadsetClientCall} still used by some 270 * clients. 271 */ toLegacyCall(HfpClientCall call)272 static BluetoothHeadsetClientCall toLegacyCall(HfpClientCall call) { 273 if (call == null) return null; 274 return new BluetoothHeadsetClientCall( 275 call.getDevice(), 276 call.getId(), 277 call.getUUID(), 278 call.getState(), 279 call.getNumber(), 280 call.isMultiParty(), 281 call.isOutgoing(), 282 call.isInBandRing()); 283 } 284 285 // API methods getHeadsetClientService()286 public static synchronized HeadsetClientService getHeadsetClientService() { 287 if (sHeadsetClientService == null) { 288 Log.w(TAG, "getHeadsetClientService(): service is null"); 289 return null; 290 } 291 if (!sHeadsetClientService.isAvailable()) { 292 Log.w(TAG, "getHeadsetClientService(): service is not available "); 293 return null; 294 } 295 return sHeadsetClientService; 296 } 297 298 /** Set a {@link HeadsetClientService} instance. */ 299 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) setHeadsetClientService(HeadsetClientService instance)300 public static synchronized void setHeadsetClientService(HeadsetClientService instance) { 301 Log.d(TAG, "setHeadsetClientService(): set to: " + instance); 302 sHeadsetClientService = instance; 303 } 304 connect(BluetoothDevice device)305 public boolean connect(BluetoothDevice device) { 306 Log.d(TAG, "connect " + device); 307 if (getConnectionPolicy(device) == CONNECTION_POLICY_FORBIDDEN) { 308 Log.w( 309 TAG, 310 "Connection not allowed: <" 311 + device.getAddress() 312 + "> is CONNECTION_POLICY_FORBIDDEN"); 313 return false; 314 } 315 HeadsetClientStateMachine sm = getStateMachine(device, true); 316 if (sm == null) { 317 Log.e(TAG, "Cannot allocate SM for device " + device); 318 return false; 319 } 320 321 sm.sendMessage(HeadsetClientStateMachine.CONNECT, device); 322 return true; 323 } 324 325 /** 326 * Disconnects hfp client for the remote bluetooth device 327 * 328 * @param device is the device with which we are attempting to disconnect the profile 329 * @return true if hfp client profile successfully disconnected, false otherwise 330 */ disconnect(BluetoothDevice device)331 public boolean disconnect(BluetoothDevice device) { 332 HeadsetClientStateMachine sm = getStateMachine(device); 333 if (sm == null) { 334 Log.e(TAG, "SM does not exist for device " + device); 335 return false; 336 } 337 338 int connectionState = sm.getConnectionState(device); 339 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 340 return false; 341 } 342 343 sm.sendMessage(HeadsetClientStateMachine.DISCONNECT, device); 344 return true; 345 } 346 347 /** 348 * @return A list of connected {@link BluetoothDevice}. 349 */ getConnectedDevices()350 public List<BluetoothDevice> getConnectedDevices() { 351 ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); 352 synchronized (mStateMachineMap) { 353 for (BluetoothDevice bd : mStateMachineMap.keySet()) { 354 HeadsetClientStateMachine sm = mStateMachineMap.get(bd); 355 if (sm != null && sm.getConnectionState(bd) == STATE_CONNECTED) { 356 connectedDevices.add(bd); 357 } 358 } 359 } 360 return connectedDevices; 361 } 362 getDevicesMatchingConnectionStates(int[] states)363 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 364 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 365 synchronized (mStateMachineMap) { 366 for (BluetoothDevice bd : mStateMachineMap.keySet()) { 367 for (int state : states) { 368 HeadsetClientStateMachine sm = mStateMachineMap.get(bd); 369 if (sm != null && sm.getConnectionState(bd) == state) { 370 devices.add(bd); 371 } 372 } 373 } 374 } 375 return devices; 376 } 377 378 /** 379 * Get the current connection state of the profile 380 * 381 * @param device is the remote bluetooth device 382 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, {@link 383 * BluetoothProfile#STATE_CONNECTING} if this profile is being connected, {@link 384 * BluetoothProfile#STATE_CONNECTED} if this profile is connected, or {@link 385 * BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 386 */ getConnectionState(BluetoothDevice device)387 public int getConnectionState(BluetoothDevice device) { 388 HeadsetClientStateMachine sm = getStateMachine(device); 389 if (sm != null) { 390 return sm.getConnectionState(device); 391 } 392 393 return STATE_DISCONNECTED; 394 } 395 396 /** 397 * Set connection policy of the profile and connects it if connectionPolicy is {@link 398 * BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is {@link 399 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 400 * 401 * <p>The device should already be paired. Connection policy can be one of: {@link 402 * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link 403 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 404 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 405 * 406 * @param device Paired bluetooth device 407 * @param connectionPolicy is the connection policy to set to for this profile 408 * @return true if connectionPolicy is set, false on error 409 */ setConnectionPolicy(BluetoothDevice device, int connectionPolicy)410 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 411 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 412 413 if (!mDatabaseManager.setProfileConnectionPolicy( 414 device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy)) { 415 return false; 416 } 417 if (connectionPolicy == CONNECTION_POLICY_ALLOWED) { 418 connect(device); 419 } else if (connectionPolicy == CONNECTION_POLICY_FORBIDDEN) { 420 disconnect(device); 421 } 422 return true; 423 } 424 425 /** 426 * Get the connection policy of the profile. 427 * 428 * <p>The connection policy can be any of: {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 429 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 430 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 431 * 432 * @param device Bluetooth device 433 * @return connection policy of the device 434 */ getConnectionPolicy(BluetoothDevice device)435 public int getConnectionPolicy(BluetoothDevice device) { 436 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT); 437 } 438 startVoiceRecognition(BluetoothDevice device)439 boolean startVoiceRecognition(BluetoothDevice device) { 440 HeadsetClientStateMachine sm = getStateMachine(device); 441 if (sm == null) { 442 Log.e(TAG, "SM does not exist for device " + device); 443 return false; 444 } 445 int connectionState = sm.getConnectionState(device); 446 if (connectionState != STATE_CONNECTED) { 447 return false; 448 } 449 sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START); 450 return true; 451 } 452 stopVoiceRecognition(BluetoothDevice device)453 boolean stopVoiceRecognition(BluetoothDevice device) { 454 HeadsetClientStateMachine sm = getStateMachine(device); 455 if (sm == null) { 456 Log.e(TAG, "SM does not exist for device " + device); 457 return false; 458 } 459 int connectionState = sm.getConnectionState(device); 460 if (connectionState != STATE_CONNECTED) { 461 return false; 462 } 463 sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_STOP); 464 return true; 465 } 466 467 /** 468 * Gets audio state of the connection with {@code device}. 469 * 470 * <p>Can be one of {@link STATE_AUDIO_CONNECTED}, {@link STATE_AUDIO_CONNECTING}, or {@link 471 * STATE_AUDIO_DISCONNECTED}. 472 */ getAudioState(BluetoothDevice device)473 public int getAudioState(BluetoothDevice device) { 474 HeadsetClientStateMachine sm = getStateMachine(device); 475 if (sm == null) { 476 Log.e(TAG, "SM does not exist for device " + device); 477 return -1; 478 } 479 480 return sm.getAudioState(device); 481 } 482 setAudioRouteAllowed(BluetoothDevice device, boolean allowed)483 public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { 484 Log.i( 485 TAG, 486 "setAudioRouteAllowed: device=" 487 + device 488 + ", allowed=" 489 + allowed 490 + ", " 491 + Utils.getUidPidString()); 492 synchronized (mStateMachineMap) { 493 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 494 if (sm != null) { 495 sm.setAudioRouteAllowed(allowed); 496 } 497 } 498 } 499 getAudioRouteAllowed(BluetoothDevice device)500 public boolean getAudioRouteAllowed(BluetoothDevice device) { 501 synchronized (mStateMachineMap) { 502 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 503 if (sm != null) { 504 return sm.getAudioRouteAllowed(); 505 } 506 } 507 return false; 508 } 509 510 /** 511 * sends the {@link BluetoothSinkAudioPolicy} object to the state machine of the corresponding 512 * device to store and send to the remote device using Android specific AT commands. 513 * 514 * @param device for whom the policies to be set 515 * @param policies to be set policies 516 */ setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies)517 public void setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies) { 518 Log.i( 519 TAG, 520 "setAudioPolicy: device=" 521 + device 522 + ", " 523 + policies.toString() 524 + ", " 525 + Utils.getUidPidString()); 526 HeadsetClientStateMachine sm = getStateMachine(device); 527 if (sm != null) { 528 sm.setAudioPolicy(policies); 529 } 530 } 531 532 /** 533 * sets the audio policy feature support status for the corresponding device. 534 * 535 * @param device for whom the policies to be set 536 * @param supported support status 537 */ setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported)538 public void setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported) { 539 Log.i(TAG, "setAudioPolicyRemoteSupported: " + supported); 540 HeadsetClientStateMachine sm = getStateMachine(device); 541 if (sm != null) { 542 sm.setAudioPolicyRemoteSupported(supported); 543 } 544 } 545 546 /** 547 * gets the audio policy feature support status for the corresponding device. 548 * 549 * @param device for whom the policies to be set 550 * @return int support status 551 */ getAudioPolicyRemoteSupported(BluetoothDevice device)552 public int getAudioPolicyRemoteSupported(BluetoothDevice device) { 553 HeadsetClientStateMachine sm = getStateMachine(device); 554 if (sm != null) { 555 return sm.getAudioPolicyRemoteSupported(); 556 } 557 return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED; 558 } 559 connectAudio(BluetoothDevice device)560 public boolean connectAudio(BluetoothDevice device) { 561 Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString()); 562 HeadsetClientStateMachine sm = getStateMachine(device); 563 if (sm == null) { 564 Log.e(TAG, "SM does not exist for device " + device); 565 return false; 566 } 567 568 if (!sm.isConnected()) { 569 return false; 570 } 571 if (sm.isAudioOn()) { 572 return false; 573 } 574 sm.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO); 575 return true; 576 } 577 disconnectAudio(BluetoothDevice device)578 public boolean disconnectAudio(BluetoothDevice device) { 579 HeadsetClientStateMachine sm = getStateMachine(device); 580 if (sm == null) { 581 Log.e(TAG, "SM does not exist for device " + device); 582 return false; 583 } 584 585 if (!sm.isAudioOn()) { 586 return false; 587 } 588 sm.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO); 589 return true; 590 } 591 holdCall(BluetoothDevice device)592 public boolean holdCall(BluetoothDevice device) { 593 HeadsetClientStateMachine sm = getStateMachine(device); 594 if (sm == null) { 595 Log.e(TAG, "SM does not exist for device " + device); 596 return false; 597 } 598 599 int connectionState = sm.getConnectionState(device); 600 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 601 return false; 602 } 603 Message msg = sm.obtainMessage(HeadsetClientStateMachine.HOLD_CALL); 604 sm.sendMessage(msg); 605 return true; 606 } 607 acceptCall(BluetoothDevice device, int flag)608 public boolean acceptCall(BluetoothDevice device, int flag) { 609 /* Phone calls from a single device are supported, hang up any calls on the other phone */ 610 synchronized (mStateMachineMap) { 611 for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : 612 mStateMachineMap.entrySet()) { 613 if (entry.getValue() == null || entry.getKey().equals(device)) { 614 continue; 615 } 616 int connectionState = entry.getValue().getConnectionState(entry.getKey()); 617 Log.d( 618 TAG, 619 "Accepting a call on device " 620 + device 621 + ". Possibly disconnecting on " 622 + entry.getValue()); 623 if (connectionState == STATE_CONNECTED) { 624 entry.getValue() 625 .obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL) 626 .sendToTarget(); 627 } 628 } 629 } 630 HeadsetClientStateMachine sm = getStateMachine(device); 631 if (sm == null) { 632 Log.e(TAG, "SM does not exist for device " + device); 633 return false; 634 } 635 636 int connectionState = sm.getConnectionState(device); 637 if (connectionState != STATE_CONNECTED) { 638 return false; 639 } 640 Message msg = sm.obtainMessage(HeadsetClientStateMachine.ACCEPT_CALL); 641 msg.arg1 = flag; 642 sm.sendMessage(msg); 643 return true; 644 } 645 rejectCall(BluetoothDevice device)646 public boolean rejectCall(BluetoothDevice device) { 647 HeadsetClientStateMachine sm = getStateMachine(device); 648 if (sm == null) { 649 Log.e(TAG, "SM does not exist for device " + device); 650 return false; 651 } 652 653 int connectionState = sm.getConnectionState(device); 654 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 655 return false; 656 } 657 658 Message msg = sm.obtainMessage(HeadsetClientStateMachine.REJECT_CALL); 659 sm.sendMessage(msg); 660 return true; 661 } 662 terminateCall(BluetoothDevice device, UUID uuid)663 public boolean terminateCall(BluetoothDevice device, UUID uuid) { 664 HeadsetClientStateMachine sm = getStateMachine(device); 665 if (sm == null) { 666 Log.e(TAG, "SM does not exist for device " + device); 667 return false; 668 } 669 670 int connectionState = sm.getConnectionState(device); 671 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 672 return false; 673 } 674 675 Message msg = sm.obtainMessage(HeadsetClientStateMachine.TERMINATE_CALL); 676 msg.obj = uuid; 677 sm.sendMessage(msg); 678 return true; 679 } 680 enterPrivateMode(BluetoothDevice device, int index)681 public boolean enterPrivateMode(BluetoothDevice device, int index) { 682 HeadsetClientStateMachine sm = getStateMachine(device); 683 if (sm == null) { 684 Log.e(TAG, "SM does not exist for device " + device); 685 return false; 686 } 687 688 int connectionState = sm.getConnectionState(device); 689 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 690 return false; 691 } 692 693 Message msg = sm.obtainMessage(HeadsetClientStateMachine.ENTER_PRIVATE_MODE); 694 msg.arg1 = index; 695 sm.sendMessage(msg); 696 return true; 697 } 698 dial(BluetoothDevice device, String number)699 public HfpClientCall dial(BluetoothDevice device, String number) { 700 HeadsetClientStateMachine sm = getStateMachine(device); 701 if (sm == null) { 702 Log.e(TAG, "SM does not exist for device " + device); 703 return null; 704 } 705 706 int connectionState = sm.getConnectionState(device); 707 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 708 return null; 709 } 710 711 // Some platform does not support three way calling (ex: watch) 712 final boolean support_three_way_calling = 713 SystemProperties.getBoolean( 714 "bluetooth.headset_client.three_way_calling.enabled", true); 715 if (!support_three_way_calling && !getCurrentCalls(device).isEmpty()) { 716 Log.e(TAG, "dial(" + device + "): Line is busy, reject dialing"); 717 return null; 718 } 719 720 HfpClientCall call = 721 new HfpClientCall( 722 device, 723 HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID, 724 HfpClientCall.CALL_STATE_DIALING, 725 number, 726 false /* multiparty */, 727 true /* outgoing */, 728 sm.getInBandRing()); 729 Message msg = sm.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER); 730 msg.obj = call; 731 sm.sendMessage(msg); 732 return call; 733 } 734 sendDTMF(BluetoothDevice device, byte code)735 public boolean sendDTMF(BluetoothDevice device, byte code) { 736 HeadsetClientStateMachine sm = getStateMachine(device); 737 if (sm == null) { 738 Log.e(TAG, "SM does not exist for device " + device); 739 return false; 740 } 741 742 int connectionState = sm.getConnectionState(device); 743 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 744 return false; 745 } 746 Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_DTMF); 747 msg.arg1 = code; 748 sm.sendMessage(msg); 749 return true; 750 } 751 getLastVoiceTagNumber(BluetoothDevice device)752 public boolean getLastVoiceTagNumber(BluetoothDevice device) { 753 return false; 754 } 755 getCurrentCalls(BluetoothDevice device)756 public List<HfpClientCall> getCurrentCalls(BluetoothDevice device) { 757 HeadsetClientStateMachine sm = getStateMachine(device); 758 if (sm == null) { 759 Log.e(TAG, "SM does not exist for device " + device); 760 return null; 761 } 762 763 int connectionState = sm.getConnectionState(device); 764 if (connectionState != STATE_CONNECTED) { 765 return null; 766 } 767 return sm.getCurrentCalls(); 768 } 769 explicitCallTransfer(BluetoothDevice device)770 public boolean explicitCallTransfer(BluetoothDevice device) { 771 HeadsetClientStateMachine sm = getStateMachine(device); 772 if (sm == null) { 773 Log.e(TAG, "SM does not exist for device " + device); 774 return false; 775 } 776 777 int connectionState = sm.getConnectionState(device); 778 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 779 return false; 780 } 781 Message msg = sm.obtainMessage(HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER); 782 sm.sendMessage(msg); 783 return true; 784 } 785 786 /** Send vendor AT command. */ sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand)787 public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { 788 HeadsetClientStateMachine sm = getStateMachine(device); 789 if (sm == null) { 790 Log.e(TAG, "SM does not exist for device " + device); 791 return false; 792 } 793 794 int connectionState = sm.getConnectionState(device); 795 if (connectionState != STATE_CONNECTED) { 796 return false; 797 } 798 799 Message msg = 800 sm.obtainMessage( 801 HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand); 802 sm.sendMessage(msg); 803 return true; 804 } 805 getCurrentAgEvents(BluetoothDevice device)806 public Bundle getCurrentAgEvents(BluetoothDevice device) { 807 HeadsetClientStateMachine sm = getStateMachine(device); 808 if (sm == null) { 809 Log.e(TAG, "SM does not exist for device " + device); 810 return null; 811 } 812 813 int connectionState = sm.getConnectionState(device); 814 if (connectionState != STATE_CONNECTED) { 815 return null; 816 } 817 return sm.getCurrentAgEvents(); 818 } 819 getCurrentAgFeaturesBundle(BluetoothDevice device)820 public Bundle getCurrentAgFeaturesBundle(BluetoothDevice device) { 821 HeadsetClientStateMachine sm = getStateMachine(device); 822 if (sm == null) { 823 Log.e(TAG, "SM does not exist for device " + device); 824 return null; 825 } 826 int connectionState = sm.getConnectionState(device); 827 if (connectionState != STATE_CONNECTED) { 828 return null; 829 } 830 return sm.getCurrentAgFeaturesBundle(); 831 } 832 getCurrentAgFeatures(BluetoothDevice device)833 public Set<Integer> getCurrentAgFeatures(BluetoothDevice device) { 834 HeadsetClientStateMachine sm = getStateMachine(device); 835 if (sm == null) { 836 Log.e(TAG, "SM does not exist for device " + device); 837 return null; 838 } 839 int connectionState = sm.getConnectionState(device); 840 if (connectionState != STATE_CONNECTED) { 841 return null; 842 } 843 return sm.getCurrentAgFeatures(); 844 } 845 846 // Handle messages from native (JNI) to java messageFromNative(StackEvent stackEvent)847 public void messageFromNative(StackEvent stackEvent) { 848 requireNonNull(stackEvent.device); 849 850 HeadsetClientStateMachine sm = 851 getStateMachine(stackEvent.device, isConnectionEvent(stackEvent)); 852 if (sm == null) { 853 throw new IllegalStateException( 854 "State machine not found for stack event: " + stackEvent); 855 } 856 sm.sendMessage(StackEvent.STACK_EVENT, stackEvent); 857 } 858 isConnectionEvent(StackEvent stackEvent)859 private static boolean isConnectionEvent(StackEvent stackEvent) { 860 if (stackEvent.type == StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 861 if ((stackEvent.valueInt == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING) 862 || (stackEvent.valueInt 863 == HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED)) { 864 return true; 865 } 866 } 867 return false; 868 } 869 getStateMachine(BluetoothDevice device)870 private HeadsetClientStateMachine getStateMachine(BluetoothDevice device) { 871 return getStateMachine(device, false); 872 } 873 getStateMachine( BluetoothDevice device, boolean isConnectionEvent)874 private HeadsetClientStateMachine getStateMachine( 875 BluetoothDevice device, boolean isConnectionEvent) { 876 if (device == null) { 877 Log.e(TAG, "getStateMachine failed: Device cannot be null"); 878 return null; 879 } 880 881 HeadsetClientStateMachine sm; 882 synchronized (mStateMachineMap) { 883 sm = mStateMachineMap.get(device); 884 } 885 886 if (sm != null) { 887 Log.d(TAG, "Found SM for device " + device); 888 } else if (isConnectionEvent) { 889 // The only time a new state machine should be created when none was found is for 890 // connection events. 891 sm = allocateStateMachine(device); 892 if (sm == null) { 893 Log.e(TAG, "SM could not be allocated for device " + device); 894 } 895 } 896 return sm; 897 } 898 allocateStateMachine(BluetoothDevice device)899 private HeadsetClientStateMachine allocateStateMachine(BluetoothDevice device) { 900 if (device == null) { 901 Log.e(TAG, "allocateStateMachine failed: Device cannot be null"); 902 return null; 903 } 904 905 if (getHeadsetClientService() == null) { 906 // Preconditions: {@code setHeadsetClientService(this)} is the last thing {@code start} 907 // does, and {@code setHeadsetClientService(null)} is (one of) the first thing 908 // {@code stop does}. 909 Log.e( 910 TAG, 911 "Cannot allocate SM if service has begun stopping or has not completed" 912 + " startup."); 913 return null; 914 } 915 916 synchronized (mStateMachineMap) { 917 HeadsetClientStateMachine sm = mStateMachineMap.get(device); 918 if (sm != null) { 919 Log.d(TAG, "allocateStateMachine: SM already exists for device " + device); 920 return sm; 921 } 922 923 // There is a possibility of a DOS attack if someone populates here with a lot of fake 924 // BluetoothAddresses. If it so happens instead of blowing up we can at least put a 925 // limit on how long the attack would survive 926 if (mStateMachineMap.keySet().size() > MAX_STATE_MACHINES_POSSIBLE) { 927 Log.e( 928 TAG, 929 "Max state machines reached, possible DOS attack " 930 + MAX_STATE_MACHINES_POSSIBLE); 931 return null; 932 } 933 934 // Allocate a new SM 935 Log.d(TAG, "Creating a new state machine"); 936 sm = mSmFactory.make(mAdapterService, this, mSmThread, mNativeInterface); 937 mStateMachineMap.put(device, sm); 938 return sm; 939 } 940 } 941 942 // Check if any of the state machines have routed the SCO audio stream. isScoRouted()943 boolean isScoRouted() { 944 synchronized (mStateMachineMap) { 945 for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : 946 mStateMachineMap.entrySet()) { 947 if (entry.getValue() != null) { 948 int audioState = entry.getValue().getAudioState(entry.getKey()); 949 if (audioState == HeadsetClientHalConstants.AUDIO_STATE_CONNECTED) { 950 Log.d( 951 TAG, 952 "Device " 953 + entry.getKey() 954 + " audio state " 955 + audioState 956 + " Connected"); 957 return true; 958 } 959 } 960 } 961 } 962 return false; 963 } 964 handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel)965 void handleBatteryLevelChanged(BluetoothDevice device, int batteryLevel) { 966 mAdapterService.getRemoteDevices().handleAgBatteryLevelChanged(device, batteryLevel); 967 } 968 969 @Override dump(StringBuilder sb)970 public void dump(StringBuilder sb) { 971 super.dump(sb); 972 synchronized (mStateMachineMap) { 973 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 974 if (sm != null) { 975 sm.dump(sb); 976 } 977 } 978 979 sb.append("\n"); 980 HfpClientConnectionService.dump(sb); 981 } 982 } 983 984 // For testing getStateMachineMap()985 protected Map<BluetoothDevice, HeadsetClientStateMachine> getStateMachineMap() { 986 synchronized (mStateMachineMap) { 987 return mStateMachineMap; 988 } 989 } 990 getAudioManager()991 protected AudioManager getAudioManager() { 992 return mAudioManager; 993 } 994 updateBatteryLevel()995 protected void updateBatteryLevel() { 996 int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); 997 int batteryIndicatorID = 2; 998 999 synchronized (mStateMachineMap) { 1000 for (HeadsetClientStateMachine sm : mStateMachineMap.values()) { 1001 if (sm != null) { 1002 sm.sendMessage( 1003 HeadsetClientStateMachine.SEND_BIEV, batteryIndicatorID, batteryLevel); 1004 } 1005 } 1006 } 1007 } 1008 } 1009