1 /* 2 * Copyright (C) 2012 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.hfp; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.MODIFY_PHONE_STATE; 21 22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 23 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHeadset; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.BluetoothUuid; 30 import android.bluetooth.IBluetoothHeadset; 31 import android.content.Attributable; 32 import android.content.AttributionSource; 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.net.Uri; 39 import android.os.BatteryManager; 40 import android.os.Handler; 41 import android.os.HandlerThread; 42 import android.os.Looper; 43 import android.os.ParcelUuid; 44 import android.os.SystemProperties; 45 import android.os.UserHandle; 46 import android.telecom.PhoneAccount; 47 import android.util.Log; 48 49 import com.android.bluetooth.BluetoothMetricsProto; 50 import com.android.bluetooth.BluetoothStatsLog; 51 import com.android.bluetooth.Utils; 52 import com.android.bluetooth.btservice.AdapterService; 53 import com.android.bluetooth.btservice.MetricsLogger; 54 import com.android.bluetooth.btservice.ProfileService; 55 import com.android.bluetooth.btservice.storage.DatabaseManager; 56 import com.android.internal.annotations.VisibleForTesting; 57 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Comparator; 61 import java.util.HashMap; 62 import java.util.List; 63 import java.util.Objects; 64 65 /** 66 * Provides Bluetooth Headset and Handsfree profile, as a service in the Bluetooth application. 67 * 68 * Three modes for SCO audio: 69 * Mode 1: Telecom call through {@link #phoneStateChanged(int, int, int, String, int, String, 70 * boolean)} 71 * Mode 2: Virtual call through {@link #startScoUsingVirtualVoiceCall()} 72 * Mode 3: Voice recognition through {@link #startVoiceRecognition(BluetoothDevice)} 73 * 74 * When one mode is active, other mode cannot be started. API user has to terminate existing modes 75 * using the correct API or just {@link #disconnectAudio()} if user is a system service, before 76 * starting a new mode. 77 * 78 * {@link #connectAudio()} will start SCO audio at one of the above modes, but won't change mode 79 * {@link #disconnectAudio()} can happen in any mode to disconnect SCO 80 * 81 * When audio is disconnected, only Mode 1 Telecom call will be persisted, both Mode 2 virtual call 82 * and Mode 3 voice call will be terminated upon SCO termination and client has to restart the mode. 83 * 84 * NOTE: SCO termination can either be initiated on the AG side or the HF side 85 * TODO(b/79660380): As a workaround, voice recognition will be terminated if virtual call or 86 * Telecom call is initiated while voice recognition is ongoing, in case calling app did not call 87 * {@link #stopVoiceRecognition(BluetoothDevice)} 88 * 89 * AG - Audio Gateway, device running this {@link HeadsetService}, e.g. Android Phone 90 * HF - Handsfree device, device running headset client, e.g. Wireless headphones or car kits 91 */ 92 public class HeadsetService extends ProfileService { 93 private static final String TAG = "HeadsetService"; 94 private static final boolean DBG = false; 95 private static final String DISABLE_INBAND_RINGING_PROPERTY = 96 "persist.bluetooth.disableinbandringing"; 97 private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.HFP}; 98 private static final int[] CONNECTING_CONNECTED_STATES = 99 {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED}; 100 private static final int DIALING_OUT_TIMEOUT_MS = 10000; 101 102 private int mMaxHeadsetConnections = 1; 103 private BluetoothDevice mActiveDevice; 104 private AdapterService mAdapterService; 105 private DatabaseManager mDatabaseManager; 106 private HandlerThread mStateMachinesThread; 107 private Handler mStateMachinesThreadHandler; 108 // This is also used as a lock for shared data in HeadsetService 109 private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>(); 110 private HeadsetNativeInterface mNativeInterface; 111 private HeadsetSystemInterface mSystemInterface; 112 private boolean mAudioRouteAllowed = true; 113 // Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions 114 private boolean mForceScoAudio; 115 private boolean mInbandRingingRuntimeDisable; 116 private boolean mVirtualCallStarted; 117 // Non null value indicates a pending dialing out event is going on 118 private DialingOutTimeoutEvent mDialingOutTimeoutEvent; 119 private boolean mVoiceRecognitionStarted; 120 // Non null value indicates a pending voice recognition request from headset is going on 121 private VoiceRecognitionTimeoutEvent mVoiceRecognitionTimeoutEvent; 122 // Timeout when voice recognition is started by remote device 123 @VisibleForTesting static int sStartVrTimeoutMs = 5000; 124 private boolean mStarted; 125 private boolean mCreated; 126 private static HeadsetService sHeadsetService; 127 128 @Override initBinder()129 public IProfileServiceBinder initBinder() { 130 return new BluetoothHeadsetBinder(this); 131 } 132 133 @Override create()134 protected void create() { 135 Log.i(TAG, "create()"); 136 if (mCreated) { 137 throw new IllegalStateException("create() called twice"); 138 } 139 mCreated = true; 140 } 141 142 @Override start()143 protected boolean start() { 144 Log.i(TAG, "start()"); 145 if (mStarted) { 146 throw new IllegalStateException("start() called twice"); 147 } 148 // Step 1: Get AdapterService and DatabaseManager, should never be null 149 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 150 "AdapterService cannot be null when HeadsetService starts"); 151 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 152 "DatabaseManager cannot be null when HeadsetService starts"); 153 // Step 2: Start handler thread for state machines 154 mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines"); 155 mStateMachinesThread.start(); 156 // Step 3: Initialize system interface 157 mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this); 158 // Step 4: Initialize native interface 159 mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices(); 160 mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface(); 161 // Add 1 to allow a pending device to be connecting or disconnecting 162 mNativeInterface.init(mMaxHeadsetConnections + 1, isInbandRingingEnabled()); 163 // Step 5: Check if state machine table is empty, crash if not 164 if (mStateMachines.size() > 0) { 165 throw new IllegalStateException( 166 "start(): mStateMachines is not empty, " + mStateMachines.size() 167 + " is already created. Was stop() called properly?"); 168 } 169 // Step 6: Setup broadcast receivers 170 IntentFilter filter = new IntentFilter(); 171 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 172 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 173 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 174 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 175 registerReceiver(mHeadsetReceiver, filter); 176 // Step 7: Mark service as started 177 setHeadsetService(this); 178 mStarted = true; 179 return true; 180 } 181 182 @Override stop()183 protected boolean stop() { 184 Log.i(TAG, "stop()"); 185 if (!mStarted) { 186 Log.w(TAG, "stop() called before start()"); 187 // Still return true because it is considered "stopped" and doesn't have any functional 188 // impact on the user 189 return true; 190 } 191 // Step 7: Mark service as stopped 192 mStarted = false; 193 setHeadsetService(null); 194 // Step 6: Tear down broadcast receivers 195 unregisterReceiver(mHeadsetReceiver); 196 synchronized (mStateMachines) { 197 // Reset active device to null 198 mActiveDevice = null; 199 mInbandRingingRuntimeDisable = false; 200 mForceScoAudio = false; 201 mAudioRouteAllowed = true; 202 mMaxHeadsetConnections = 1; 203 mVoiceRecognitionStarted = false; 204 mVirtualCallStarted = false; 205 if (mDialingOutTimeoutEvent != null) { 206 getStateMachinesThreadHandler() 207 .removeCallbacks(mDialingOutTimeoutEvent); 208 mDialingOutTimeoutEvent = null; 209 } 210 if (mVoiceRecognitionTimeoutEvent != null) { 211 getStateMachinesThreadHandler() 212 .removeCallbacks(mVoiceRecognitionTimeoutEvent); 213 mVoiceRecognitionTimeoutEvent = null; 214 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 215 mSystemInterface.getVoiceRecognitionWakeLock().release(); 216 } 217 } 218 // Step 5: Destroy state machines 219 for (HeadsetStateMachine stateMachine : mStateMachines.values()) { 220 HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine); 221 } 222 mStateMachines.clear(); 223 } 224 // Step 4: Destroy native interface 225 mNativeInterface.cleanup(); 226 // Step 3: Destroy system interface 227 mSystemInterface.stop(); 228 // Step 2: Stop handler thread 229 mStateMachinesThread.quitSafely(); 230 mStateMachinesThread = null; 231 mStateMachinesThreadHandler = null; 232 // Step 1: Clear 233 synchronized (mStateMachines) { 234 mAdapterService = null; 235 } 236 return true; 237 } 238 239 @Override cleanup()240 protected void cleanup() { 241 Log.i(TAG, "cleanup"); 242 if (!mCreated) { 243 Log.w(TAG, "cleanup() called before create()"); 244 } 245 mCreated = false; 246 } 247 248 /** 249 * Checks if this service object is able to accept binder calls 250 * 251 * @return True if the object can accept binder calls, False otherwise 252 */ isAlive()253 public boolean isAlive() { 254 return isAvailable() && mCreated && mStarted; 255 } 256 257 /** 258 * Get the {@link Looper} for the state machine thread. This is used in testing and helper 259 * objects 260 * 261 * @return {@link Looper} for the state machine thread 262 */ 263 @VisibleForTesting getStateMachinesThreadLooper()264 public Looper getStateMachinesThreadLooper() { 265 return mStateMachinesThread.getLooper(); 266 } 267 268 interface StateMachineTask { execute(HeadsetStateMachine stateMachine)269 void execute(HeadsetStateMachine stateMachine); 270 } 271 doForStateMachine(BluetoothDevice device, StateMachineTask task)272 private boolean doForStateMachine(BluetoothDevice device, StateMachineTask task) { 273 synchronized (mStateMachines) { 274 HeadsetStateMachine stateMachine = mStateMachines.get(device); 275 if (stateMachine == null) { 276 return false; 277 } 278 task.execute(stateMachine); 279 } 280 return true; 281 } 282 doForEachConnectedStateMachine(StateMachineTask task)283 private void doForEachConnectedStateMachine(StateMachineTask task) { 284 synchronized (mStateMachines) { 285 for (BluetoothDevice device : getConnectedDevices()) { 286 task.execute(mStateMachines.get(device)); 287 } 288 } 289 } 290 onDeviceStateChanged(HeadsetDeviceState deviceState)291 void onDeviceStateChanged(HeadsetDeviceState deviceState) { 292 doForEachConnectedStateMachine( 293 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED, 294 deviceState)); 295 } 296 297 /** 298 * Handle messages from native (JNI) to Java. This needs to be synchronized to avoid posting 299 * messages to state machine before start() is done 300 * 301 * @param stackEvent event from native stack 302 */ messageFromNative(HeadsetStackEvent stackEvent)303 void messageFromNative(HeadsetStackEvent stackEvent) { 304 Objects.requireNonNull(stackEvent.device, 305 "Device should never be null, event: " + stackEvent); 306 synchronized (mStateMachines) { 307 HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device); 308 if (stackEvent.type == HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 309 switch (stackEvent.valueInt) { 310 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED: 311 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: { 312 // Create new state machine if none is found 313 if (stateMachine == null) { 314 stateMachine = HeadsetObjectsFactory.getInstance() 315 .makeStateMachine(stackEvent.device, 316 mStateMachinesThread.getLooper(), this, mAdapterService, 317 mNativeInterface, mSystemInterface); 318 mStateMachines.put(stackEvent.device, stateMachine); 319 } 320 break; 321 } 322 } 323 } 324 if (stateMachine == null) { 325 throw new IllegalStateException( 326 "State machine not found for stack event: " + stackEvent); 327 } 328 stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent); 329 } 330 } 331 332 private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { 333 @Override 334 public void onReceive(Context context, Intent intent) { 335 String action = intent.getAction(); 336 if (action == null) { 337 Log.w(TAG, "mHeadsetReceiver, action is null"); 338 return; 339 } 340 switch (action) { 341 case Intent.ACTION_BATTERY_CHANGED: { 342 int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 343 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 344 if (batteryLevel < 0 || scale <= 0) { 345 Log.e(TAG, "Bad Battery Changed intent: batteryLevel=" + batteryLevel 346 + ", scale=" + scale); 347 return; 348 } 349 int cindBatteryLevel = Math.round(batteryLevel * 5 / ((float) scale)); 350 mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(cindBatteryLevel); 351 break; 352 } 353 case AudioManager.VOLUME_CHANGED_ACTION: { 354 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 355 if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) { 356 doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage( 357 HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent)); 358 } 359 break; 360 } 361 case BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY: { 362 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 363 BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); 364 BluetoothDevice device = 365 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 366 logD("Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, device=" + device 367 + ", type=" + requestType); 368 if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { 369 synchronized (mStateMachines) { 370 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 371 if (stateMachine == null) { 372 Log.wtf(TAG, "Cannot find state machine for " + device); 373 return; 374 } 375 stateMachine.sendMessage( 376 HeadsetStateMachine.INTENT_CONNECTION_ACCESS_REPLY, intent); 377 } 378 } 379 break; 380 } 381 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: { 382 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 383 BluetoothDevice.ERROR); 384 BluetoothDevice device = Objects.requireNonNull( 385 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), 386 "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 387 logD("Bond state changed for device: " + device + " state: " + state); 388 if (state != BluetoothDevice.BOND_NONE) { 389 break; 390 } 391 synchronized (mStateMachines) { 392 HeadsetStateMachine stateMachine = mStateMachines.get(device); 393 if (stateMachine == null) { 394 break; 395 } 396 if (stateMachine.getConnectionState() 397 != BluetoothProfile.STATE_DISCONNECTED) { 398 break; 399 } 400 removeStateMachine(device); 401 } 402 break; 403 } 404 default: 405 Log.w(TAG, "Unknown action " + action); 406 } 407 } 408 }; 409 410 /** 411 * Handlers for incoming service calls 412 */ 413 private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub 414 implements IProfileServiceBinder { 415 private volatile HeadsetService mService; 416 BluetoothHeadsetBinder(HeadsetService svc)417 BluetoothHeadsetBinder(HeadsetService svc) { 418 mService = svc; 419 } 420 421 @Override cleanup()422 public void cleanup() { 423 mService = null; 424 } 425 426 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)427 private HeadsetService getService(AttributionSource source) { 428 if (!Utils.checkCallerIsSystemOrActiveUser(TAG) 429 || !Utils.checkServiceAvailable(mService, TAG) 430 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 431 return null; 432 } 433 return mService; 434 } 435 436 @Override connect(BluetoothDevice device)437 public boolean connect(BluetoothDevice device) { 438 return connectWithAttribution(device, Utils.getCallingAttributionSource()); 439 } 440 441 @Override connectWithAttribution(BluetoothDevice device, AttributionSource source)442 public boolean connectWithAttribution(BluetoothDevice device, AttributionSource source) { 443 Attributable.setAttributionSource(device, source); 444 HeadsetService service = getService(source); 445 if (service == null) { 446 return false; 447 } 448 return service.connect(device); 449 } 450 451 @Override disconnect(BluetoothDevice device)452 public boolean disconnect(BluetoothDevice device) { 453 return disconnectWithAttribution(device, Utils.getCallingAttributionSource()); 454 } 455 456 @Override disconnectWithAttribution(BluetoothDevice device, AttributionSource source)457 public boolean disconnectWithAttribution(BluetoothDevice device, AttributionSource source) { 458 Attributable.setAttributionSource(device, source); 459 HeadsetService service = getService(source); 460 if (service == null) { 461 return false; 462 } 463 return service.disconnect(device); 464 } 465 466 @Override getConnectedDevices()467 public List<BluetoothDevice> getConnectedDevices() { 468 return getConnectedDevicesWithAttribution(Utils.getCallingAttributionSource()); 469 } 470 471 @Override getConnectedDevicesWithAttribution(AttributionSource source)472 public List<BluetoothDevice> getConnectedDevicesWithAttribution(AttributionSource source) { 473 HeadsetService service = getService(source); 474 if (service == null) { 475 return new ArrayList<BluetoothDevice>(0); 476 } 477 return service.getConnectedDevices(); 478 } 479 480 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source)481 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states, 482 AttributionSource source) { 483 HeadsetService service = getService(source); 484 if (service == null) { 485 return new ArrayList<BluetoothDevice>(0); 486 } 487 return service.getDevicesMatchingConnectionStates(states); 488 } 489 490 @Override getConnectionState(BluetoothDevice device)491 public int getConnectionState(BluetoothDevice device) { 492 return getConnectionStateWithAttribution(device, Utils.getCallingAttributionSource()); 493 } 494 495 @Override getConnectionStateWithAttribution(BluetoothDevice device, AttributionSource source)496 public int getConnectionStateWithAttribution(BluetoothDevice device, 497 AttributionSource source) { 498 Attributable.setAttributionSource(device, source); 499 HeadsetService service = getService(source); 500 if (service == null) { 501 return BluetoothProfile.STATE_DISCONNECTED; 502 } 503 return service.getConnectionState(device); 504 } 505 506 @Override setPriority(BluetoothDevice device, int connectionPolicy, AttributionSource source)507 public boolean setPriority(BluetoothDevice device, int connectionPolicy, 508 AttributionSource source) { 509 Attributable.setAttributionSource(device, source); 510 HeadsetService service = getService(source); 511 if (service == null) { 512 return false; 513 } 514 return service.setConnectionPolicy(device, connectionPolicy); 515 } 516 517 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source)518 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 519 AttributionSource source) { 520 Attributable.setAttributionSource(device, source); 521 HeadsetService service = getService(source); 522 if (service == null) { 523 return false; 524 } 525 enforceBluetoothPrivilegedPermission(service); 526 return service.setConnectionPolicy(device, connectionPolicy); 527 } 528 529 @Override getPriority(BluetoothDevice device, AttributionSource source)530 public int getPriority(BluetoothDevice device, AttributionSource source) { 531 Attributable.setAttributionSource(device, source); 532 HeadsetService service = getService(source); 533 if (service == null) { 534 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 535 } 536 return service.getConnectionPolicy(device); 537 } 538 539 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source)540 public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) { 541 Attributable.setAttributionSource(device, source); 542 HeadsetService service = getService(source); 543 if (service == null) { 544 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 545 } 546 enforceBluetoothPrivilegedPermission(service); 547 return service.getConnectionPolicy(device); 548 } 549 550 @Override isNoiseReductionSupported(BluetoothDevice device, AttributionSource source)551 public boolean isNoiseReductionSupported(BluetoothDevice device, AttributionSource source) { 552 Attributable.setAttributionSource(device, source); 553 HeadsetService service = getService(source); 554 if (service == null) { 555 return false; 556 } 557 return service.isNoiseReductionSupported(device); 558 } 559 560 @Override isVoiceRecognitionSupported(BluetoothDevice device, AttributionSource source)561 public boolean isVoiceRecognitionSupported(BluetoothDevice device, 562 AttributionSource source) { 563 Attributable.setAttributionSource(device, source); 564 HeadsetService service = getService(source); 565 if (service == null) { 566 return false; 567 } 568 return service.isVoiceRecognitionSupported(device); 569 } 570 571 @Override startVoiceRecognition(BluetoothDevice device, AttributionSource source)572 public boolean startVoiceRecognition(BluetoothDevice device, AttributionSource source) { 573 Attributable.setAttributionSource(device, source); 574 HeadsetService service = getService(source); 575 if (service == null) { 576 return false; 577 } 578 return service.startVoiceRecognition(device); 579 } 580 581 @Override stopVoiceRecognition(BluetoothDevice device, AttributionSource source)582 public boolean stopVoiceRecognition(BluetoothDevice device, AttributionSource source) { 583 Attributable.setAttributionSource(device, source); 584 HeadsetService service = getService(source); 585 if (service == null) { 586 return false; 587 } 588 return service.stopVoiceRecognition(device); 589 } 590 591 @Override isAudioOn(AttributionSource source)592 public boolean isAudioOn(AttributionSource source) { 593 HeadsetService service = getService(source); 594 if (service == null) { 595 return false; 596 } 597 return service.isAudioOn(); 598 } 599 600 @Override isAudioConnected(BluetoothDevice device, AttributionSource source)601 public boolean isAudioConnected(BluetoothDevice device, AttributionSource source) { 602 Attributable.setAttributionSource(device, source); 603 HeadsetService service = getService(source); 604 if (service == null) { 605 return false; 606 } 607 return service.isAudioConnected(device); 608 } 609 610 @Override getAudioState(BluetoothDevice device, AttributionSource source)611 public int getAudioState(BluetoothDevice device, AttributionSource source) { 612 Attributable.setAttributionSource(device, source); 613 HeadsetService service = getService(source); 614 if (service == null) { 615 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 616 } 617 return service.getAudioState(device); 618 } 619 620 @Override connectAudio(AttributionSource source)621 public boolean connectAudio(AttributionSource source) { 622 HeadsetService service = getService(source); 623 if (service == null) { 624 return false; 625 } 626 return service.connectAudio(); 627 } 628 629 @Override disconnectAudio(AttributionSource source)630 public boolean disconnectAudio(AttributionSource source) { 631 HeadsetService service = getService(source); 632 if (service == null) { 633 return false; 634 } 635 return service.disconnectAudio(); 636 } 637 638 @Override setAudioRouteAllowed(boolean allowed, AttributionSource source)639 public void setAudioRouteAllowed(boolean allowed, AttributionSource source) { 640 HeadsetService service = getService(source); 641 if (service == null) { 642 return; 643 } 644 service.setAudioRouteAllowed(allowed); 645 } 646 647 @Override getAudioRouteAllowed(AttributionSource source)648 public boolean getAudioRouteAllowed(AttributionSource source) { 649 HeadsetService service = getService(source); 650 if (service != null) { 651 return service.getAudioRouteAllowed(); 652 } 653 return false; 654 } 655 656 @Override setForceScoAudio(boolean forced, AttributionSource source)657 public void setForceScoAudio(boolean forced, AttributionSource source) { 658 HeadsetService service = getService(source); 659 if (service == null) { 660 return; 661 } 662 service.setForceScoAudio(forced); 663 } 664 665 @Override startScoUsingVirtualVoiceCall(AttributionSource source)666 public boolean startScoUsingVirtualVoiceCall(AttributionSource source) { 667 HeadsetService service = getService(source); 668 if (service == null) { 669 return false; 670 } 671 return service.startScoUsingVirtualVoiceCall(); 672 } 673 674 @Override stopScoUsingVirtualVoiceCall(AttributionSource source)675 public boolean stopScoUsingVirtualVoiceCall(AttributionSource source) { 676 HeadsetService service = getService(source); 677 if (service == null) { 678 return false; 679 } 680 return service.stopScoUsingVirtualVoiceCall(); 681 } 682 683 @Override phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name, AttributionSource source)684 public void phoneStateChanged(int numActive, int numHeld, int callState, String number, 685 int type, String name, AttributionSource source) { 686 HeadsetService service = getService(source); 687 if (service == null) { 688 return; 689 } 690 service.phoneStateChanged(numActive, numHeld, callState, number, type, name, false); 691 } 692 693 @Override clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type, AttributionSource source)694 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 695 String number, int type, AttributionSource source) { 696 HeadsetService service = getService(source); 697 if (service == null) { 698 return; 699 } 700 service.clccResponse(index, direction, status, mode, mpty, number, type); 701 } 702 703 @Override sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg, AttributionSource source)704 public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 705 String arg, AttributionSource source) { 706 Attributable.setAttributionSource(device, source); 707 HeadsetService service = getService(source); 708 if (service == null) { 709 return false; 710 } 711 return service.sendVendorSpecificResultCode(device, command, arg); 712 } 713 714 @Override setActiveDevice(BluetoothDevice device, AttributionSource source)715 public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) { 716 Attributable.setAttributionSource(device, source); 717 HeadsetService service = getService(source); 718 if (service == null) { 719 return false; 720 } 721 return service.setActiveDevice(device); 722 } 723 724 @Override getActiveDevice(AttributionSource source)725 public BluetoothDevice getActiveDevice(AttributionSource source) { 726 HeadsetService service = getService(source); 727 if (service == null) { 728 return null; 729 } 730 return service.getActiveDevice(); 731 } 732 733 @Override isInbandRingingEnabled(AttributionSource source)734 public boolean isInbandRingingEnabled(AttributionSource source) { 735 HeadsetService service = getService(source); 736 if (service == null) { 737 return false; 738 } 739 return service.isInbandRingingEnabled(); 740 } 741 } 742 743 // API methods getHeadsetService()744 public static synchronized HeadsetService getHeadsetService() { 745 if (sHeadsetService == null) { 746 Log.w(TAG, "getHeadsetService(): service is NULL"); 747 return null; 748 } 749 if (!sHeadsetService.isAvailable()) { 750 Log.w(TAG, "getHeadsetService(): service is not available"); 751 return null; 752 } 753 logD("getHeadsetService(): returning " + sHeadsetService); 754 return sHeadsetService; 755 } 756 setHeadsetService(HeadsetService instance)757 private static synchronized void setHeadsetService(HeadsetService instance) { 758 logD("setHeadsetService(): set to: " + instance); 759 sHeadsetService = instance; 760 } 761 762 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) connect(BluetoothDevice device)763 public boolean connect(BluetoothDevice device) { 764 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 765 Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", " 766 + Utils.getUidPidString()); 767 return false; 768 } 769 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 770 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) { 771 Log.e(TAG, "connect: Cannot connect to " + device + ": no headset UUID, " 772 + Utils.getUidPidString()); 773 return false; 774 } 775 synchronized (mStateMachines) { 776 Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString()); 777 HeadsetStateMachine stateMachine = mStateMachines.get(device); 778 if (stateMachine == null) { 779 stateMachine = HeadsetObjectsFactory.getInstance() 780 .makeStateMachine(device, mStateMachinesThread.getLooper(), this, 781 mAdapterService, mNativeInterface, mSystemInterface); 782 mStateMachines.put(device, stateMachine); 783 } 784 int connectionState = stateMachine.getConnectionState(); 785 if (connectionState == BluetoothProfile.STATE_CONNECTED 786 || connectionState == BluetoothProfile.STATE_CONNECTING) { 787 Log.w(TAG, "connect: device " + device 788 + " is already connected/connecting, connectionState=" + connectionState); 789 return false; 790 } 791 List<BluetoothDevice> connectingConnectedDevices = 792 getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES); 793 boolean disconnectExisting = false; 794 if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) { 795 // When there is maximum one device, we automatically disconnect the current one 796 if (mMaxHeadsetConnections == 1) { 797 disconnectExisting = true; 798 } else { 799 Log.w(TAG, "Max connection has reached, rejecting connection to " + device); 800 return false; 801 } 802 } 803 if (disconnectExisting) { 804 for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) { 805 disconnect(connectingConnectedDevice); 806 } 807 setActiveDevice(null); 808 } 809 stateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 810 } 811 return true; 812 } 813 814 /** 815 * Disconnects hfp from the passed in device 816 * 817 * @param device is the device with which we will disconnect hfp 818 * @return true if hfp is disconnected, false if the device is not connected 819 */ disconnect(BluetoothDevice device)820 public boolean disconnect(BluetoothDevice device) { 821 Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString()); 822 synchronized (mStateMachines) { 823 HeadsetStateMachine stateMachine = mStateMachines.get(device); 824 if (stateMachine == null) { 825 Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting"); 826 return false; 827 } 828 int connectionState = stateMachine.getConnectionState(); 829 if (connectionState != BluetoothProfile.STATE_CONNECTED 830 && connectionState != BluetoothProfile.STATE_CONNECTING) { 831 Log.w(TAG, "disconnect: device " + device 832 + " not connected/connecting, connectionState=" + connectionState); 833 return false; 834 } 835 stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 836 } 837 return true; 838 } 839 getConnectedDevices()840 public List<BluetoothDevice> getConnectedDevices() { 841 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 842 synchronized (mStateMachines) { 843 for (HeadsetStateMachine stateMachine : mStateMachines.values()) { 844 if (stateMachine.getConnectionState() == BluetoothProfile.STATE_CONNECTED) { 845 devices.add(stateMachine.getDevice()); 846 } 847 } 848 } 849 return devices; 850 } 851 852 /** 853 * Same as the API method {@link BluetoothHeadset#getDevicesMatchingConnectionStates(int[])} 854 * 855 * @param states an array of states from {@link BluetoothProfile} 856 * @return a list of devices matching the array of connection states 857 */ 858 @VisibleForTesting getDevicesMatchingConnectionStates(int[] states)859 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 860 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 861 synchronized (mStateMachines) { 862 if (states == null || mAdapterService == null) { 863 return devices; 864 } 865 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 866 if (bondedDevices == null) { 867 return devices; 868 } 869 for (BluetoothDevice device : bondedDevices) { 870 final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 871 if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) { 872 continue; 873 } 874 int connectionState = getConnectionState(device); 875 for (int state : states) { 876 if (connectionState == state) { 877 devices.add(device); 878 break; 879 } 880 } 881 } 882 } 883 return devices; 884 } 885 getConnectionState(BluetoothDevice device)886 public int getConnectionState(BluetoothDevice device) { 887 synchronized (mStateMachines) { 888 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 889 if (stateMachine == null) { 890 return BluetoothProfile.STATE_DISCONNECTED; 891 } 892 return stateMachine.getConnectionState(); 893 } 894 } 895 896 /** 897 * Set connection policy of the profile and connects it if connectionPolicy is 898 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 899 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 900 * 901 * <p> The device should already be paired. 902 * Connection policy can be one of: 903 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 904 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 905 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 906 * 907 * @param device Paired bluetooth device 908 * @param connectionPolicy is the connection policy to set to for this profile 909 * @return true if connectionPolicy is set, false on error 910 */ 911 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)912 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 913 Log.i(TAG, "setConnectionPolicy: device=" + device 914 + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString()); 915 916 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, 917 connectionPolicy)) { 918 return false; 919 } 920 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 921 connect(device); 922 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 923 disconnect(device); 924 } 925 return true; 926 } 927 928 /** 929 * Get the connection policy of the profile. 930 * 931 * <p> The connection policy can be any of: 932 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 933 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 934 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 935 * 936 * @param device Bluetooth device 937 * @return connection policy of the device 938 * @hide 939 */ getConnectionPolicy(BluetoothDevice device)940 public int getConnectionPolicy(BluetoothDevice device) { 941 return mDatabaseManager 942 .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET); 943 } 944 isNoiseReductionSupported(BluetoothDevice device)945 boolean isNoiseReductionSupported(BluetoothDevice device) { 946 return mNativeInterface.isNoiseReductionSupported(device); 947 } 948 isVoiceRecognitionSupported(BluetoothDevice device)949 boolean isVoiceRecognitionSupported(BluetoothDevice device) { 950 return mNativeInterface.isVoiceRecognitionSupported(device); 951 } 952 953 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) startVoiceRecognition(BluetoothDevice device)954 boolean startVoiceRecognition(BluetoothDevice device) { 955 Log.i(TAG, "startVoiceRecognition: device=" + device + ", " + Utils.getUidPidString()); 956 synchronized (mStateMachines) { 957 // TODO(b/79660380): Workaround in case voice recognition was not terminated properly 958 if (mVoiceRecognitionStarted) { 959 boolean status = stopVoiceRecognition(mActiveDevice); 960 Log.w(TAG, "startVoiceRecognition: voice recognition is still active, just called " 961 + "stopVoiceRecognition, returned " + status + " on " + mActiveDevice 962 + ", please try again"); 963 mVoiceRecognitionStarted = false; 964 return false; 965 } 966 if (!isAudioModeIdle()) { 967 Log.w(TAG, "startVoiceRecognition: audio mode not idle, active device is " 968 + mActiveDevice); 969 return false; 970 } 971 // Audio should not be on when no audio mode is active 972 if (isAudioOn()) { 973 // Disconnect audio so that API user can try later 974 boolean status = disconnectAudio(); 975 Log.w(TAG, "startVoiceRecognition: audio is still active, please wait for audio to" 976 + " be disconnected, disconnectAudio() returned " + status 977 + ", active device is " + mActiveDevice); 978 return false; 979 } 980 if (device == null) { 981 Log.i(TAG, "device is null, use active device " + mActiveDevice + " instead"); 982 device = mActiveDevice; 983 } 984 boolean pendingRequestByHeadset = false; 985 if (mVoiceRecognitionTimeoutEvent != null) { 986 if (!mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice.equals(device)) { 987 // TODO(b/79660380): Workaround when target device != requesting device 988 Log.w(TAG, "startVoiceRecognition: device " + device 989 + " is not the same as requesting device " 990 + mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice 991 + ", fall back to requesting device"); 992 device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice; 993 } 994 getStateMachinesThreadHandler().removeCallbacks(mVoiceRecognitionTimeoutEvent); 995 mVoiceRecognitionTimeoutEvent = null; 996 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 997 mSystemInterface.getVoiceRecognitionWakeLock().release(); 998 } 999 pendingRequestByHeadset = true; 1000 } 1001 if (!Objects.equals(device, mActiveDevice) && !setActiveDevice(device)) { 1002 Log.w(TAG, "startVoiceRecognition: failed to set " + device + " as active"); 1003 return false; 1004 } 1005 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1006 if (stateMachine == null) { 1007 Log.w(TAG, "startVoiceRecognition: " + device + " is never connected"); 1008 return false; 1009 } 1010 int connectionState = stateMachine.getConnectionState(); 1011 if (connectionState != BluetoothProfile.STATE_CONNECTED 1012 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1013 Log.w(TAG, "startVoiceRecognition: " + device + " is not connected or connecting"); 1014 return false; 1015 } 1016 mVoiceRecognitionStarted = true; 1017 if (pendingRequestByHeadset) { 1018 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_RESULT, 1019 1 /* success */, 0, device); 1020 } else { 1021 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device); 1022 } 1023 stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); 1024 } 1025 return true; 1026 } 1027 stopVoiceRecognition(BluetoothDevice device)1028 boolean stopVoiceRecognition(BluetoothDevice device) { 1029 Log.i(TAG, "stopVoiceRecognition: device=" + device + ", " + Utils.getUidPidString()); 1030 synchronized (mStateMachines) { 1031 if (!Objects.equals(mActiveDevice, device)) { 1032 Log.w(TAG, "startVoiceRecognition: requested device " + device 1033 + " is not active, use active device " + mActiveDevice + " instead"); 1034 device = mActiveDevice; 1035 } 1036 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1037 if (stateMachine == null) { 1038 Log.w(TAG, "stopVoiceRecognition: " + device + " is never connected"); 1039 return false; 1040 } 1041 int connectionState = stateMachine.getConnectionState(); 1042 if (connectionState != BluetoothProfile.STATE_CONNECTED 1043 && connectionState != BluetoothProfile.STATE_CONNECTING) { 1044 Log.w(TAG, "stopVoiceRecognition: " + device + " is not connected or connecting"); 1045 return false; 1046 } 1047 if (!mVoiceRecognitionStarted) { 1048 Log.w(TAG, "stopVoiceRecognition: voice recognition was not started"); 1049 return false; 1050 } 1051 mVoiceRecognitionStarted = false; 1052 stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device); 1053 stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); 1054 } 1055 return true; 1056 } 1057 isAudioOn()1058 boolean isAudioOn() { 1059 return getNonIdleAudioDevices().size() > 0; 1060 } 1061 isAudioConnected(BluetoothDevice device)1062 boolean isAudioConnected(BluetoothDevice device) { 1063 synchronized (mStateMachines) { 1064 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1065 if (stateMachine == null) { 1066 return false; 1067 } 1068 return stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED; 1069 } 1070 } 1071 getAudioState(BluetoothDevice device)1072 int getAudioState(BluetoothDevice device) { 1073 synchronized (mStateMachines) { 1074 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1075 if (stateMachine == null) { 1076 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 1077 } 1078 return stateMachine.getAudioState(); 1079 } 1080 } 1081 setAudioRouteAllowed(boolean allowed)1082 public void setAudioRouteAllowed(boolean allowed) { 1083 Log.i(TAG, "setAudioRouteAllowed: allowed=" + allowed + ", " + Utils.getUidPidString()); 1084 mAudioRouteAllowed = allowed; 1085 mNativeInterface.setScoAllowed(allowed); 1086 } 1087 getAudioRouteAllowed()1088 public boolean getAudioRouteAllowed() { 1089 return mAudioRouteAllowed; 1090 } 1091 setForceScoAudio(boolean forced)1092 public void setForceScoAudio(boolean forced) { 1093 Log.i(TAG, "setForceScoAudio: forced=" + forced + ", " + Utils.getUidPidString()); 1094 mForceScoAudio = forced; 1095 } 1096 1097 @VisibleForTesting getForceScoAudio()1098 public boolean getForceScoAudio() { 1099 return mForceScoAudio; 1100 } 1101 1102 /** 1103 * Get first available device for SCO audio 1104 * 1105 * @return first connected headset device 1106 */ 1107 @VisibleForTesting 1108 @Nullable getFirstConnectedAudioDevice()1109 public BluetoothDevice getFirstConnectedAudioDevice() { 1110 ArrayList<HeadsetStateMachine> stateMachines = new ArrayList<>(); 1111 synchronized (mStateMachines) { 1112 List<BluetoothDevice> availableDevices = 1113 getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES); 1114 for (BluetoothDevice device : availableDevices) { 1115 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1116 if (stateMachine == null) { 1117 continue; 1118 } 1119 stateMachines.add(stateMachine); 1120 } 1121 } 1122 stateMachines.sort(Comparator.comparingLong(HeadsetStateMachine::getConnectingTimestampMs)); 1123 if (stateMachines.size() > 0) { 1124 return stateMachines.get(0).getDevice(); 1125 } 1126 return null; 1127 } 1128 1129 /** 1130 * Process a change in the silence mode for a {@link BluetoothDevice}. 1131 * 1132 * @param device the device to change silence mode 1133 * @param silence true to enable silence mode, false to disable. 1134 * @return true on success, false on error 1135 */ 1136 @VisibleForTesting 1137 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) setSilenceMode(BluetoothDevice device, boolean silence)1138 public boolean setSilenceMode(BluetoothDevice device, boolean silence) { 1139 Log.d(TAG, "setSilenceMode(" + device + "): " + silence); 1140 1141 if (silence && Objects.equals(mActiveDevice, device)) { 1142 setActiveDevice(null); 1143 } else if (!silence && mActiveDevice == null) { 1144 // Set the device as the active device if currently no active device. 1145 setActiveDevice(device); 1146 } 1147 synchronized (mStateMachines) { 1148 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1149 if (stateMachine == null) { 1150 Log.w(TAG, "setSilenceMode: device " + device 1151 + " was never connected/connecting"); 1152 return false; 1153 } 1154 stateMachine.setSilenceDevice(silence); 1155 } 1156 1157 return true; 1158 } 1159 1160 /** 1161 * Set the active device. 1162 * 1163 * @param device the active device 1164 * @return true on success, otherwise false 1165 */ 1166 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) setActiveDevice(BluetoothDevice device)1167 public boolean setActiveDevice(BluetoothDevice device) { 1168 Log.i(TAG, "setActiveDevice: device=" + device + ", " + Utils.getUidPidString()); 1169 synchronized (mStateMachines) { 1170 if (device == null) { 1171 // Clear the active device 1172 if (mVoiceRecognitionStarted) { 1173 if (!stopVoiceRecognition(mActiveDevice)) { 1174 Log.w(TAG, "setActiveDevice: fail to stopVoiceRecognition from " 1175 + mActiveDevice); 1176 } 1177 } 1178 if (mVirtualCallStarted) { 1179 if (!stopScoUsingVirtualVoiceCall()) { 1180 Log.w(TAG, "setActiveDevice: fail to stopScoUsingVirtualVoiceCall from " 1181 + mActiveDevice); 1182 } 1183 } 1184 if (getAudioState(mActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1185 if (!disconnectAudio(mActiveDevice)) { 1186 Log.w(TAG, "setActiveDevice: disconnectAudio failed on " + mActiveDevice); 1187 } 1188 } 1189 mActiveDevice = null; 1190 broadcastActiveDevice(null); 1191 return true; 1192 } 1193 if (device.equals(mActiveDevice)) { 1194 Log.i(TAG, "setActiveDevice: device " + device + " is already active"); 1195 return true; 1196 } 1197 if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { 1198 Log.e(TAG, "setActiveDevice: Cannot set " + device 1199 + " as active, device is not connected"); 1200 return false; 1201 } 1202 if (!mNativeInterface.setActiveDevice(device)) { 1203 Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer"); 1204 return false; 1205 } 1206 BluetoothDevice previousActiveDevice = mActiveDevice; 1207 mActiveDevice = device; 1208 if (getAudioState(previousActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1209 if (!disconnectAudio(previousActiveDevice)) { 1210 Log.e(TAG, "setActiveDevice: fail to disconnectAudio from " 1211 + previousActiveDevice); 1212 mActiveDevice = previousActiveDevice; 1213 mNativeInterface.setActiveDevice(previousActiveDevice); 1214 return false; 1215 } 1216 broadcastActiveDevice(mActiveDevice); 1217 } else if (shouldPersistAudio()) { 1218 broadcastActiveDevice(mActiveDevice); 1219 if (!connectAudio(mActiveDevice)) { 1220 Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice); 1221 mActiveDevice = previousActiveDevice; 1222 mNativeInterface.setActiveDevice(previousActiveDevice); 1223 return false; 1224 } 1225 } else { 1226 broadcastActiveDevice(mActiveDevice); 1227 } 1228 } 1229 return true; 1230 } 1231 1232 /** 1233 * Get the active device. 1234 * 1235 * @return the active device or null if no device is active 1236 */ getActiveDevice()1237 public BluetoothDevice getActiveDevice() { 1238 synchronized (mStateMachines) { 1239 return mActiveDevice; 1240 } 1241 } 1242 connectAudio()1243 boolean connectAudio() { 1244 synchronized (mStateMachines) { 1245 BluetoothDevice device = mActiveDevice; 1246 if (device == null) { 1247 Log.w(TAG, "connectAudio: no active device, " + Utils.getUidPidString()); 1248 return false; 1249 } 1250 return connectAudio(device); 1251 } 1252 } 1253 connectAudio(BluetoothDevice device)1254 boolean connectAudio(BluetoothDevice device) { 1255 Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString()); 1256 synchronized (mStateMachines) { 1257 if (!isScoAcceptable(device)) { 1258 Log.w(TAG, "connectAudio, rejected SCO request to " + device); 1259 return false; 1260 } 1261 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1262 if (stateMachine == null) { 1263 Log.w(TAG, "connectAudio: device " + device + " was never connected/connecting"); 1264 return false; 1265 } 1266 if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { 1267 Log.w(TAG, "connectAudio: profile not connected"); 1268 return false; 1269 } 1270 if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1271 logD("connectAudio: audio is not idle for device " + device); 1272 return true; 1273 } 1274 if (isAudioOn()) { 1275 Log.w(TAG, "connectAudio: audio is not idle, current audio devices are " 1276 + Arrays.toString(getNonIdleAudioDevices().toArray())); 1277 return false; 1278 } 1279 stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device); 1280 } 1281 return true; 1282 } 1283 getNonIdleAudioDevices()1284 private List<BluetoothDevice> getNonIdleAudioDevices() { 1285 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 1286 synchronized (mStateMachines) { 1287 for (HeadsetStateMachine stateMachine : mStateMachines.values()) { 1288 if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1289 devices.add(stateMachine.getDevice()); 1290 } 1291 } 1292 } 1293 return devices; 1294 } 1295 disconnectAudio()1296 boolean disconnectAudio() { 1297 boolean result = false; 1298 synchronized (mStateMachines) { 1299 for (BluetoothDevice device : getNonIdleAudioDevices()) { 1300 if (disconnectAudio(device)) { 1301 result = true; 1302 } else { 1303 Log.e(TAG, "disconnectAudio() from " + device + " failed"); 1304 } 1305 } 1306 } 1307 if (!result) { 1308 logD("disconnectAudio() no active audio connection"); 1309 } 1310 return result; 1311 } 1312 disconnectAudio(BluetoothDevice device)1313 boolean disconnectAudio(BluetoothDevice device) { 1314 synchronized (mStateMachines) { 1315 Log.i(TAG, "disconnectAudio: device=" + device + ", " + Utils.getUidPidString()); 1316 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1317 if (stateMachine == null) { 1318 Log.w(TAG, "disconnectAudio: device " + device + " was never connected/connecting"); 1319 return false; 1320 } 1321 if (stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1322 Log.w(TAG, "disconnectAudio, audio is already disconnected for " + device); 1323 return false; 1324 } 1325 stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device); 1326 } 1327 return true; 1328 } 1329 isVirtualCallStarted()1330 boolean isVirtualCallStarted() { 1331 synchronized (mStateMachines) { 1332 return mVirtualCallStarted; 1333 } 1334 } 1335 1336 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) startScoUsingVirtualVoiceCall()1337 private boolean startScoUsingVirtualVoiceCall() { 1338 Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); 1339 synchronized (mStateMachines) { 1340 // TODO(b/79660380): Workaround in case voice recognition was not terminated properly 1341 if (mVoiceRecognitionStarted) { 1342 boolean status = stopVoiceRecognition(mActiveDevice); 1343 Log.w(TAG, "startScoUsingVirtualVoiceCall: voice recognition is still active, " 1344 + "just called stopVoiceRecognition, returned " + status + " on " 1345 + mActiveDevice + ", please try again"); 1346 mVoiceRecognitionStarted = false; 1347 return false; 1348 } 1349 if (!isAudioModeIdle()) { 1350 Log.w(TAG, "startScoUsingVirtualVoiceCall: audio mode not idle, active device is " 1351 + mActiveDevice); 1352 return false; 1353 } 1354 // Audio should not be on when no audio mode is active 1355 if (isAudioOn()) { 1356 // Disconnect audio so that API user can try later 1357 boolean status = disconnectAudio(); 1358 Log.w(TAG, "startScoUsingVirtualVoiceCall: audio is still active, please wait for " 1359 + "audio to be disconnected, disconnectAudio() returned " + status 1360 + ", active device is " + mActiveDevice); 1361 return false; 1362 } 1363 if (mActiveDevice == null) { 1364 Log.w(TAG, "startScoUsingVirtualVoiceCall: no active device"); 1365 return false; 1366 } 1367 mVirtualCallStarted = true; 1368 // Send virtual phone state changed to initialize SCO 1369 phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, "", true); 1370 phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, "", true); 1371 phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true); 1372 return true; 1373 } 1374 } 1375 1376 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) stopScoUsingVirtualVoiceCall()1377 boolean stopScoUsingVirtualVoiceCall() { 1378 Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); 1379 synchronized (mStateMachines) { 1380 // 1. Check if virtual call has already started 1381 if (!mVirtualCallStarted) { 1382 Log.w(TAG, "stopScoUsingVirtualVoiceCall: virtual call not started"); 1383 return false; 1384 } 1385 mVirtualCallStarted = false; 1386 // 2. Send virtual phone state changed to close SCO 1387 phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, "", true); 1388 } 1389 return true; 1390 } 1391 1392 class DialingOutTimeoutEvent implements Runnable { 1393 BluetoothDevice mDialingOutDevice; 1394 DialingOutTimeoutEvent(BluetoothDevice fromDevice)1395 DialingOutTimeoutEvent(BluetoothDevice fromDevice) { 1396 mDialingOutDevice = fromDevice; 1397 } 1398 1399 @Override run()1400 public void run() { 1401 synchronized (mStateMachines) { 1402 mDialingOutTimeoutEvent = null; 1403 doForStateMachine(mDialingOutDevice, stateMachine -> stateMachine.sendMessage( 1404 HeadsetStateMachine.DIALING_OUT_RESULT, 0 /* fail */, 0, 1405 mDialingOutDevice)); 1406 } 1407 } 1408 1409 @Override toString()1410 public String toString() { 1411 return "DialingOutTimeoutEvent[" + mDialingOutDevice + "]"; 1412 } 1413 } 1414 1415 /** 1416 * Dial an outgoing call as requested by the remote device 1417 * 1418 * @param fromDevice remote device that initiated this dial out action 1419 * @param dialNumber number to dial 1420 * @return true on successful dial out 1421 */ 1422 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 1423 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber)1424 public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) { 1425 synchronized (mStateMachines) { 1426 Log.i(TAG, "dialOutgoingCall: from " + fromDevice); 1427 if (!isOnStateMachineThread()) { 1428 Log.e(TAG, "dialOutgoingCall must be called from state machine thread"); 1429 return false; 1430 } 1431 if (mDialingOutTimeoutEvent != null) { 1432 Log.e(TAG, "dialOutgoingCall, already dialing by " + mDialingOutTimeoutEvent); 1433 return false; 1434 } 1435 if (isVirtualCallStarted()) { 1436 if (!stopScoUsingVirtualVoiceCall()) { 1437 Log.e(TAG, "dialOutgoingCall failed to stop current virtual call"); 1438 return false; 1439 } 1440 } 1441 if (!setActiveDevice(fromDevice)) { 1442 Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice); 1443 return false; 1444 } 1445 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 1446 Uri.fromParts(PhoneAccount.SCHEME_TEL, dialNumber, null)); 1447 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1448 startActivity(intent); 1449 mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice); 1450 getStateMachinesThreadHandler() 1451 .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS); 1452 return true; 1453 } 1454 } 1455 1456 /** 1457 * Check if any connected headset has started dialing calls 1458 * 1459 * @return true if some device has started dialing calls 1460 */ 1461 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) hasDeviceInitiatedDialingOut()1462 public boolean hasDeviceInitiatedDialingOut() { 1463 synchronized (mStateMachines) { 1464 return mDialingOutTimeoutEvent != null; 1465 } 1466 } 1467 1468 class VoiceRecognitionTimeoutEvent implements Runnable { 1469 BluetoothDevice mVoiceRecognitionDevice; 1470 VoiceRecognitionTimeoutEvent(BluetoothDevice device)1471 VoiceRecognitionTimeoutEvent(BluetoothDevice device) { 1472 mVoiceRecognitionDevice = device; 1473 } 1474 1475 @Override run()1476 public void run() { 1477 synchronized (mStateMachines) { 1478 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 1479 mSystemInterface.getVoiceRecognitionWakeLock().release(); 1480 } 1481 mVoiceRecognitionTimeoutEvent = null; 1482 doForStateMachine(mVoiceRecognitionDevice, stateMachine -> stateMachine.sendMessage( 1483 HeadsetStateMachine.VOICE_RECOGNITION_RESULT, 0 /* fail */, 0, 1484 mVoiceRecognitionDevice)); 1485 } 1486 } 1487 1488 @Override toString()1489 public String toString() { 1490 return "VoiceRecognitionTimeoutEvent[" + mVoiceRecognitionDevice + "]"; 1491 } 1492 } 1493 1494 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) startVoiceRecognitionByHeadset(BluetoothDevice fromDevice)1495 boolean startVoiceRecognitionByHeadset(BluetoothDevice fromDevice) { 1496 synchronized (mStateMachines) { 1497 Log.i(TAG, "startVoiceRecognitionByHeadset: from " + fromDevice); 1498 // TODO(b/79660380): Workaround in case voice recognition was not terminated properly 1499 if (mVoiceRecognitionStarted) { 1500 boolean status = stopVoiceRecognition(mActiveDevice); 1501 Log.w(TAG, "startVoiceRecognitionByHeadset: voice recognition is still active, " 1502 + "just called stopVoiceRecognition, returned " + status + " on " 1503 + mActiveDevice + ", please try again"); 1504 mVoiceRecognitionStarted = false; 1505 return false; 1506 } 1507 if (fromDevice == null) { 1508 Log.e(TAG, "startVoiceRecognitionByHeadset: fromDevice is null"); 1509 return false; 1510 } 1511 if (!isAudioModeIdle()) { 1512 Log.w(TAG, "startVoiceRecognitionByHeadset: audio mode not idle, active device is " 1513 + mActiveDevice); 1514 return false; 1515 } 1516 // Audio should not be on when no audio mode is active 1517 if (isAudioOn()) { 1518 // Disconnect audio so that user can try later 1519 boolean status = disconnectAudio(); 1520 Log.w(TAG, "startVoiceRecognitionByHeadset: audio is still active, please wait for" 1521 + " audio to be disconnected, disconnectAudio() returned " + status 1522 + ", active device is " + mActiveDevice); 1523 return false; 1524 } 1525 // Do not start new request until the current one is finished or timeout 1526 if (mVoiceRecognitionTimeoutEvent != null) { 1527 Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice 1528 + ", already pending by " + mVoiceRecognitionTimeoutEvent); 1529 return false; 1530 } 1531 if (!setActiveDevice(fromDevice)) { 1532 Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice 1533 + " as active"); 1534 return false; 1535 } 1536 if (!mSystemInterface.activateVoiceRecognition()) { 1537 Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice); 1538 return false; 1539 } 1540 mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice); 1541 getStateMachinesThreadHandler() 1542 .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs); 1543 1544 if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 1545 mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs); 1546 } 1547 return true; 1548 } 1549 } 1550 stopVoiceRecognitionByHeadset(BluetoothDevice fromDevice)1551 boolean stopVoiceRecognitionByHeadset(BluetoothDevice fromDevice) { 1552 synchronized (mStateMachines) { 1553 Log.i(TAG, "stopVoiceRecognitionByHeadset: from " + fromDevice); 1554 if (!Objects.equals(fromDevice, mActiveDevice)) { 1555 Log.w(TAG, "stopVoiceRecognitionByHeadset: " + fromDevice 1556 + " is not active, active device is " + mActiveDevice); 1557 return false; 1558 } 1559 if (!mVoiceRecognitionStarted && mVoiceRecognitionTimeoutEvent == null) { 1560 Log.w(TAG, "stopVoiceRecognitionByHeadset: voice recognition not started, device=" 1561 + fromDevice); 1562 return false; 1563 } 1564 if (mVoiceRecognitionTimeoutEvent != null) { 1565 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) { 1566 mSystemInterface.getVoiceRecognitionWakeLock().release(); 1567 } 1568 getStateMachinesThreadHandler() 1569 .removeCallbacks(mVoiceRecognitionTimeoutEvent); 1570 1571 mVoiceRecognitionTimeoutEvent = null; 1572 } 1573 if (mVoiceRecognitionStarted) { 1574 if (!disconnectAudio()) { 1575 Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from " 1576 + fromDevice); 1577 } 1578 mVoiceRecognitionStarted = false; 1579 } 1580 if (!mSystemInterface.deactivateVoiceRecognition()) { 1581 Log.w(TAG, "stopVoiceRecognitionByHeadset: failed request from " + fromDevice); 1582 return false; 1583 } 1584 return true; 1585 } 1586 } 1587 1588 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name, boolean isVirtualCall)1589 private void phoneStateChanged(int numActive, int numHeld, int callState, String number, 1590 int type, String name, boolean isVirtualCall) { 1591 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission"); 1592 synchronized (mStateMachines) { 1593 // Should stop all other audio mode in this case 1594 if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) { 1595 if (!isVirtualCall && mVirtualCallStarted) { 1596 // stop virtual voice call if there is an incoming Telecom call update 1597 stopScoUsingVirtualVoiceCall(); 1598 } 1599 if (mVoiceRecognitionStarted) { 1600 // stop voice recognition if there is any incoming call 1601 stopVoiceRecognition(mActiveDevice); 1602 } 1603 } 1604 if (mDialingOutTimeoutEvent != null) { 1605 // Send result to state machine when dialing starts 1606 if (callState == HeadsetHalConstants.CALL_STATE_DIALING) { 1607 getStateMachinesThreadHandler() 1608 .removeCallbacks(mDialingOutTimeoutEvent); 1609 doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice, 1610 stateMachine -> stateMachine.sendMessage( 1611 HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0, 1612 mDialingOutTimeoutEvent.mDialingOutDevice)); 1613 } else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE 1614 || callState == HeadsetHalConstants.CALL_STATE_IDLE) { 1615 // Clear the timeout event when the call is connected or disconnected 1616 if (!getStateMachinesThreadHandler() 1617 .hasCallbacks(mDialingOutTimeoutEvent)) { 1618 mDialingOutTimeoutEvent = null; 1619 } 1620 } 1621 } 1622 } 1623 getStateMachinesThreadHandler().post(() -> { 1624 boolean isCallIdleBefore = mSystemInterface.isCallIdle(); 1625 mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive); 1626 mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld); 1627 mSystemInterface.getHeadsetPhoneState().setCallState(callState); 1628 // Suspend A2DP when call about is about to become active 1629 if (mActiveDevice != null && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED 1630 && !mSystemInterface.isCallIdle() && isCallIdleBefore) { 1631 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true"); 1632 } 1633 }); 1634 doForEachConnectedStateMachine( 1635 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED, 1636 new HeadsetCallState(numActive, numHeld, callState, number, type, name))); 1637 getStateMachinesThreadHandler().post(() -> { 1638 if (callState == HeadsetHalConstants.CALL_STATE_IDLE 1639 && mSystemInterface.isCallIdle() && !isAudioOn()) { 1640 // Resume A2DP when call ended and SCO is not connected 1641 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false"); 1642 } 1643 }); 1644 1645 } 1646 1647 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)1648 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 1649 String number, int type) { 1650 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission"); 1651 doForEachConnectedStateMachine( 1652 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 1653 new HeadsetClccResponse(index, direction, status, mode, mpty, number, 1654 type))); 1655 } 1656 sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg)1657 private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, 1658 String arg) { 1659 synchronized (mStateMachines) { 1660 final HeadsetStateMachine stateMachine = mStateMachines.get(device); 1661 if (stateMachine == null) { 1662 Log.w(TAG, "sendVendorSpecificResultCode: device " + device 1663 + " was never connected/connecting"); 1664 return false; 1665 } 1666 int connectionState = stateMachine.getConnectionState(); 1667 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 1668 return false; 1669 } 1670 // Currently we support only "+ANDROID". 1671 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 1672 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 1673 return false; 1674 } 1675 stateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 1676 new HeadsetVendorSpecificResultCode(device, command, arg)); 1677 } 1678 return true; 1679 } 1680 isInbandRingingEnabled()1681 boolean isInbandRingingEnabled() { 1682 return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean( 1683 DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable; 1684 } 1685 1686 /** 1687 * Called from {@link HeadsetStateMachine} in state machine thread when there is a connection 1688 * state change 1689 * 1690 * @param device remote device 1691 * @param fromState from which connection state is the change 1692 * @param toState to which connection state is the change 1693 */ 1694 @VisibleForTesting 1695 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState, int toState)1696 public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState, 1697 int toState) { 1698 synchronized (mStateMachines) { 1699 List<BluetoothDevice> audioConnectableDevices = 1700 getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES); 1701 if (fromState != BluetoothProfile.STATE_CONNECTED 1702 && toState == BluetoothProfile.STATE_CONNECTED) { 1703 if (audioConnectableDevices.size() > 1) { 1704 mInbandRingingRuntimeDisable = true; 1705 doForEachConnectedStateMachine( 1706 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR, 1707 0)); 1708 } 1709 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET); 1710 } 1711 if (fromState != BluetoothProfile.STATE_DISCONNECTED 1712 && toState == BluetoothProfile.STATE_DISCONNECTED) { 1713 if (audioConnectableDevices.size() <= 1) { 1714 mInbandRingingRuntimeDisable = false; 1715 doForEachConnectedStateMachine( 1716 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR, 1717 1)); 1718 } 1719 if (device.equals(mActiveDevice)) { 1720 setActiveDevice(null); 1721 } 1722 } 1723 } 1724 } 1725 1726 /** 1727 * Check if no audio mode is active 1728 * 1729 * @return false if virtual call, voice recognition, or Telecom call is active, true if all idle 1730 */ isAudioModeIdle()1731 private boolean isAudioModeIdle() { 1732 synchronized (mStateMachines) { 1733 if (mVoiceRecognitionStarted || mVirtualCallStarted || !mSystemInterface.isCallIdle()) { 1734 Log.i(TAG, "isAudioModeIdle: not idle, mVoiceRecognitionStarted=" 1735 + mVoiceRecognitionStarted + ", mVirtualCallStarted=" + mVirtualCallStarted 1736 + ", isCallIdle=" + mSystemInterface.isCallIdle()); 1737 return false; 1738 } 1739 return true; 1740 } 1741 } 1742 shouldCallAudioBeActive()1743 private boolean shouldCallAudioBeActive() { 1744 return mSystemInterface.isInCall() || (mSystemInterface.isRinging() 1745 && isInbandRingingEnabled()); 1746 } 1747 1748 /** 1749 * Only persist audio during active device switch when call audio is supposed to be active and 1750 * virtual call has not been started. Virtual call is ignored because AudioService and 1751 * applications should reconnect SCO during active device switch and forcing SCO connection 1752 * here will make AudioService think SCO is started externally instead of by one of its SCO 1753 * clients. 1754 * 1755 * @return true if call audio should be active and no virtual call is going on 1756 */ shouldPersistAudio()1757 private boolean shouldPersistAudio() { 1758 return !mVirtualCallStarted && shouldCallAudioBeActive(); 1759 } 1760 1761 /** 1762 * Called from {@link HeadsetStateMachine} in state machine thread when there is a audio 1763 * connection state change 1764 * 1765 * @param device remote device 1766 * @param fromState from which audio connection state is the change 1767 * @param toState to which audio connection state is the change 1768 */ 1769 @VisibleForTesting 1770 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState, int toState)1771 public void onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState, 1772 int toState) { 1773 synchronized (mStateMachines) { 1774 if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1775 if (fromState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1776 if (mActiveDevice != null && !mActiveDevice.equals(device) 1777 && shouldPersistAudio()) { 1778 if (!connectAudio(mActiveDevice)) { 1779 Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect" 1780 + " audio to new " + "active device " + mActiveDevice 1781 + ", after " + device + " is disconnected from SCO"); 1782 } 1783 } 1784 } 1785 if (mVoiceRecognitionStarted) { 1786 if (!stopVoiceRecognitionByHeadset(device)) { 1787 Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice " 1788 + "recognition"); 1789 } 1790 } 1791 if (mVirtualCallStarted) { 1792 if (!stopScoUsingVirtualVoiceCall()) { 1793 Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop virtual " 1794 + "voice call"); 1795 } 1796 } 1797 // Unsuspend A2DP when SCO connection is gone and call state is idle 1798 if (mSystemInterface.isCallIdle()) { 1799 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false"); 1800 } 1801 } 1802 } 1803 } 1804 broadcastActiveDevice(BluetoothDevice device)1805 private void broadcastActiveDevice(BluetoothDevice device) { 1806 logD("broadcastActiveDevice: " + device); 1807 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, 1808 BluetoothProfile.HEADSET, mAdapterService.obfuscateAddress(device), 1809 mAdapterService.getMetricId(device)); 1810 Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); 1811 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1812 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 1813 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 1814 sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, 1815 Utils.getTempAllowlistBroadcastOptions()); 1816 } 1817 1818 /** 1819 * Check whether it is OK to accept a headset connection from a remote device 1820 * 1821 * @param device remote device that initiates the connection 1822 * @return true if the connection is acceptable 1823 */ okToAcceptConnection(BluetoothDevice device)1824 public boolean okToAcceptConnection(BluetoothDevice device) { 1825 // Check if this is an incoming connection in Quiet mode. 1826 if (mAdapterService.isQuietModeEnabled()) { 1827 Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled"); 1828 return false; 1829 } 1830 // Check connection policy and accept or reject the connection. 1831 int connectionPolicy = getConnectionPolicy(device); 1832 int bondState = mAdapterService.getBondState(device); 1833 // Allow this connection only if the device is bonded. Any attempt to connect while 1834 // bonding would potentially lead to an unauthorized connection. 1835 if (bondState != BluetoothDevice.BOND_BONDED) { 1836 Log.w(TAG, "okToAcceptConnection: return false, bondState=" + bondState); 1837 return false; 1838 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 1839 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 1840 // Otherwise, reject the connection if connection policy is not valid. 1841 Log.w(TAG, "okToAcceptConnection: return false, connectionPolicy=" + connectionPolicy); 1842 return false; 1843 } 1844 List<BluetoothDevice> connectingConnectedDevices = 1845 getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES); 1846 if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) { 1847 Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections 1848 + " was reached, rejecting connection from " + device); 1849 return false; 1850 } 1851 return true; 1852 } 1853 1854 /** 1855 * Checks if SCO should be connected at current system state 1856 * 1857 * @param device device for SCO to be connected 1858 * @return true if SCO is allowed to be connected 1859 */ isScoAcceptable(BluetoothDevice device)1860 public boolean isScoAcceptable(BluetoothDevice device) { 1861 synchronized (mStateMachines) { 1862 if (device == null || !device.equals(mActiveDevice)) { 1863 Log.w(TAG, "isScoAcceptable: rejected SCO since " + device 1864 + " is not the current active device " + mActiveDevice); 1865 return false; 1866 } 1867 if (mForceScoAudio) { 1868 return true; 1869 } 1870 if (!mAudioRouteAllowed) { 1871 Log.w(TAG, "isScoAcceptable: rejected SCO since audio route is not allowed"); 1872 return false; 1873 } 1874 if (mVoiceRecognitionStarted || mVirtualCallStarted) { 1875 return true; 1876 } 1877 if (shouldCallAudioBeActive()) { 1878 return true; 1879 } 1880 Log.w(TAG, "isScoAcceptable: rejected SCO, inCall=" + mSystemInterface.isInCall() 1881 + ", voiceRecognition=" + mVoiceRecognitionStarted + ", ringing=" 1882 + mSystemInterface.isRinging() + ", inbandRinging=" + isInbandRingingEnabled() 1883 + ", isVirtualCallStarted=" + mVirtualCallStarted); 1884 return false; 1885 } 1886 } 1887 1888 /** 1889 * Remove state machine in {@link #mStateMachines} for a {@link BluetoothDevice} 1890 * 1891 * @param device device whose state machine is to be removed. 1892 */ removeStateMachine(BluetoothDevice device)1893 void removeStateMachine(BluetoothDevice device) { 1894 synchronized (mStateMachines) { 1895 HeadsetStateMachine stateMachine = mStateMachines.get(device); 1896 if (stateMachine == null) { 1897 Log.w(TAG, "removeStateMachine(), " + device + " does not have a state machine"); 1898 return; 1899 } 1900 Log.i(TAG, "removeStateMachine(), removing state machine for device: " + device); 1901 HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine); 1902 mStateMachines.remove(device); 1903 } 1904 } 1905 isOnStateMachineThread()1906 private boolean isOnStateMachineThread() { 1907 final Looper myLooper = Looper.myLooper(); 1908 return myLooper != null && (mStateMachinesThread != null) && (myLooper.getThread().getId() 1909 == mStateMachinesThread.getId()); 1910 } 1911 1912 @Override dump(StringBuilder sb)1913 public void dump(StringBuilder sb) { 1914 synchronized (mStateMachines) { 1915 super.dump(sb); 1916 ProfileService.println(sb, "mMaxHeadsetConnections: " + mMaxHeadsetConnections); 1917 ProfileService.println(sb, "DefaultMaxHeadsetConnections: " 1918 + mAdapterService.getMaxConnectedAudioDevices()); 1919 ProfileService.println(sb, "mActiveDevice: " + mActiveDevice); 1920 ProfileService.println(sb, "isInbandRingingEnabled: " + isInbandRingingEnabled()); 1921 ProfileService.println(sb, 1922 "isInbandRingingSupported: " + BluetoothHeadset.isInbandRingingSupported(this)); 1923 ProfileService.println(sb, 1924 "mInbandRingingRuntimeDisable: " + mInbandRingingRuntimeDisable); 1925 ProfileService.println(sb, "mAudioRouteAllowed: " + mAudioRouteAllowed); 1926 ProfileService.println(sb, "mVoiceRecognitionStarted: " + mVoiceRecognitionStarted); 1927 ProfileService.println(sb, 1928 "mVoiceRecognitionTimeoutEvent: " + mVoiceRecognitionTimeoutEvent); 1929 ProfileService.println(sb, "mVirtualCallStarted: " + mVirtualCallStarted); 1930 ProfileService.println(sb, "mDialingOutTimeoutEvent: " + mDialingOutTimeoutEvent); 1931 ProfileService.println(sb, "mForceScoAudio: " + mForceScoAudio); 1932 ProfileService.println(sb, "mCreated: " + mCreated); 1933 ProfileService.println(sb, "mStarted: " + mStarted); 1934 ProfileService.println(sb, 1935 "AudioManager.isBluetoothScoOn(): " + mSystemInterface.getAudioManager() 1936 .isBluetoothScoOn()); 1937 ProfileService.println(sb, "Telecom.isInCall(): " + mSystemInterface.isInCall()); 1938 ProfileService.println(sb, "Telecom.isRinging(): " + mSystemInterface.isRinging()); 1939 for (HeadsetStateMachine stateMachine : mStateMachines.values()) { 1940 ProfileService.println(sb, 1941 "==== StateMachine for " + stateMachine.getDevice() + " ===="); 1942 stateMachine.dump(sb); 1943 } 1944 } 1945 } 1946 logD(String message)1947 private static void logD(String message) { 1948 if (DBG) { 1949 Log.d(TAG, message); 1950 } 1951 } 1952 getStateMachinesThreadHandler()1953 private Handler getStateMachinesThreadHandler() { 1954 if (mStateMachinesThreadHandler == null) { 1955 mStateMachinesThreadHandler = new Handler(mStateMachinesThread.getLooper()); 1956 } 1957 return mStateMachinesThreadHandler; 1958 } 1959 } 1960