1 /* 2 * Copyright 2019 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 package com.android.server.audio; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.Intent; 24 import android.media.AudioDeviceAttributes; 25 import android.media.AudioDevicePort; 26 import android.media.AudioFormat; 27 import android.media.AudioManager; 28 import android.media.AudioPort; 29 import android.media.AudioRoutesInfo; 30 import android.media.AudioSystem; 31 import android.media.IAudioRoutesObserver; 32 import android.media.ICapturePresetDevicesRoleDispatcher; 33 import android.media.IStrategyPreferredDevicesDispatcher; 34 import android.media.MediaMetrics; 35 import android.os.Binder; 36 import android.os.RemoteCallbackList; 37 import android.os.RemoteException; 38 import android.text.TextUtils; 39 import android.util.ArrayMap; 40 import android.util.ArraySet; 41 import android.util.Log; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.GuardedBy; 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 import java.util.HashSet; 50 import java.util.LinkedHashMap; 51 import java.util.List; 52 import java.util.Objects; 53 import java.util.Set; 54 import java.util.UUID; 55 56 /** 57 * Class to manage the inventory of all connected devices. 58 * This class is thread-safe. 59 * (non final for mocking/spying) 60 */ 61 public class AudioDeviceInventory { 62 63 private static final String TAG = "AS.AudioDeviceInventory"; 64 65 // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices 66 private final Object mDevicesLock = new Object(); 67 68 //Audio Analytics ids. 69 private static final String mMetricsId = "audio.device."; 70 71 // List of connected devices 72 // Key for map created from DeviceInfo.makeDeviceListKey() 73 @GuardedBy("mDevicesLock") 74 private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() { 75 @Override 76 public DeviceInfo put(String key, DeviceInfo value) { 77 final DeviceInfo result = super.put(key, value); 78 record("put", true /* connected */, key, value); 79 return result; 80 } 81 82 @Override 83 public DeviceInfo putIfAbsent(String key, DeviceInfo value) { 84 final DeviceInfo result = super.putIfAbsent(key, value); 85 if (result == null) { 86 record("putIfAbsent", true /* connected */, key, value); 87 } 88 return result; 89 } 90 91 @Override 92 public DeviceInfo remove(Object key) { 93 final DeviceInfo result = super.remove(key); 94 if (result != null) { 95 record("remove", false /* connected */, (String) key, result); 96 } 97 return result; 98 } 99 100 @Override 101 public boolean remove(Object key, Object value) { 102 final boolean result = super.remove(key, value); 103 if (result) { 104 record("remove", false /* connected */, (String) key, (DeviceInfo) value); 105 } 106 return result; 107 } 108 109 // Not overridden 110 // clear 111 // compute 112 // computeIfAbsent 113 // computeIfPresent 114 // merge 115 // putAll 116 // replace 117 // replaceAll 118 private void record(String event, boolean connected, String key, DeviceInfo value) { 119 // DeviceInfo - int mDeviceType; 120 // DeviceInfo - int mDeviceCodecFormat; 121 new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE 122 + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType)) 123 .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress) 124 .set(MediaMetrics.Property.EVENT, event) 125 .set(MediaMetrics.Property.NAME, value.mDeviceName) 126 .set(MediaMetrics.Property.STATE, connected 127 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 128 .record(); 129 } 130 }; 131 132 // List of devices actually connected to AudioPolicy (through AudioSystem), only one 133 // by device type, which is used as the key, value is the DeviceInfo generated key. 134 // For the moment only for A2DP sink devices. 135 // TODO: extend to all device types 136 @GuardedBy("mDevicesLock") 137 private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>(); 138 139 // List of preferred devices for strategies 140 private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices = 141 new ArrayMap<>(); 142 143 // List of preferred devices of capture preset 144 private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset = 145 new ArrayMap<>(); 146 147 // the wrapper for AudioSystem static methods, allows us to spy AudioSystem 148 private final @NonNull AudioSystemAdapter mAudioSystem; 149 150 private @NonNull AudioDeviceBroker mDeviceBroker; 151 152 // Monitoring of audio routes. Protected by mAudioRoutes. 153 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); 154 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = 155 new RemoteCallbackList<IAudioRoutesObserver>(); 156 157 // Monitoring of strategy-preferred device 158 final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers = 159 new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>(); 160 161 // Monitoring of devices for role and capture preset 162 final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = 163 new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); 164 AudioDeviceInventory(@onNull AudioDeviceBroker broker)165 /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { 166 mDeviceBroker = broker; 167 mAudioSystem = AudioSystemAdapter.getDefaultAdapter(); 168 } 169 170 //----------------------------------------------------------- 171 /** for mocking only, allows to inject AudioSystem adapter */ AudioDeviceInventory(@onNull AudioSystemAdapter audioSystem)172 /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) { 173 mDeviceBroker = null; 174 mAudioSystem = audioSystem; 175 } 176 setDeviceBroker(@onNull AudioDeviceBroker broker)177 /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { 178 mDeviceBroker = broker; 179 } 180 181 //------------------------------------------------------------ 182 /** 183 * Class to store info about connected devices. 184 * Use makeDeviceListKey() to make a unique key for this list. 185 */ 186 private static class DeviceInfo { 187 final int mDeviceType; 188 final @NonNull String mDeviceName; 189 final @NonNull String mDeviceAddress; 190 int mDeviceCodecFormat; 191 final UUID mSensorUuid; 192 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat, UUID sensorUuid)193 DeviceInfo(int deviceType, String deviceName, String deviceAddress, 194 int deviceCodecFormat, UUID sensorUuid) { 195 mDeviceType = deviceType; 196 mDeviceName = deviceName == null ? "" : deviceName; 197 mDeviceAddress = deviceAddress == null ? "" : deviceAddress; 198 mDeviceCodecFormat = deviceCodecFormat; 199 mSensorUuid = sensorUuid; 200 } 201 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat)202 DeviceInfo(int deviceType, String deviceName, String deviceAddress, 203 int deviceCodecFormat) { 204 this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); 205 } 206 207 @Override toString()208 public String toString() { 209 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) 210 + " (" + AudioSystem.getDeviceName(mDeviceType) 211 + ") name:" + mDeviceName 212 + " addr:" + mDeviceAddress 213 + " codec: " + Integer.toHexString(mDeviceCodecFormat) 214 + " sensorUuid: " + Objects.toString(mSensorUuid) + "]"; 215 } 216 getKey()217 @NonNull String getKey() { 218 return makeDeviceListKey(mDeviceType, mDeviceAddress); 219 } 220 221 /** 222 * Generate a unique key for the mConnectedDevices List by composing the device "type" 223 * and the "address" associated with a specific instance of that device type 224 */ makeDeviceListKey(int device, String deviceAddress)225 @NonNull private static String makeDeviceListKey(int device, String deviceAddress) { 226 return "0x" + Integer.toHexString(device) + ":" + deviceAddress; 227 } 228 } 229 230 /** 231 * A class just for packaging up a set of connection parameters. 232 */ 233 /*package*/ class WiredDeviceConnectionState { 234 public final AudioDeviceAttributes mAttributes; 235 public final @AudioService.ConnectionState int mState; 236 public final String mCaller; 237 public boolean mForTest = false; 238 WiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)239 /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes, 240 @AudioService.ConnectionState int state, String caller) { 241 mAttributes = attributes; 242 mState = state; 243 mCaller = caller; 244 } 245 } 246 247 //------------------------------------------------------------ dump(PrintWriter pw, String prefix)248 /*package*/ void dump(PrintWriter pw, String prefix) { 249 pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET="); 250 BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> { 251 pw.print(" 0x" + Integer.toHexString(device)); }); 252 pw.println("\n" + prefix + "Preferred devices for strategy:"); 253 mPreferredDevices.forEach((strategy, device) -> { 254 pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); 255 pw.println("\n" + prefix + "Connected devices:"); 256 mConnectedDevices.forEach((key, deviceInfo) -> { 257 pw.println(" " + prefix + deviceInfo.toString()); }); 258 pw.println("\n" + prefix + "APM Connected device (A2DP sink only):"); 259 mApmConnectedDevices.forEach((keyType, valueAddress) -> { 260 pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) 261 + " (" + AudioSystem.getDeviceName(keyType) 262 + ") addr:" + valueAddress); }); 263 mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { 264 pw.println(" " + prefix + "capturePreset:" + capturePreset 265 + " devices:" + devices); }); 266 } 267 268 //------------------------------------------------------------ 269 // Message handling from AudioDeviceBroker 270 271 /** 272 * Restore previously connected devices. Use in case of audio server crash 273 * (see AudioService.onAudioServerDied() method) 274 */ 275 // Always executed on AudioDeviceBroker message queue onRestoreDevices()276 /*package*/ void onRestoreDevices() { 277 synchronized (mDevicesLock) { 278 //TODO iterate on mApmConnectedDevices instead once it handles all device types 279 for (DeviceInfo di : mConnectedDevices.values()) { 280 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType, 281 di.mDeviceAddress, 282 di.mDeviceName), 283 AudioSystem.DEVICE_STATE_AVAILABLE, 284 di.mDeviceCodecFormat); 285 } 286 } 287 synchronized (mPreferredDevices) { 288 mPreferredDevices.forEach((strategy, devices) -> { 289 mAudioSystem.setDevicesRoleForStrategy( 290 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); }); 291 } 292 synchronized (mPreferredDevicesForCapturePreset) { 293 // TODO: call audiosystem to restore 294 } 295 } 296 297 // @GuardedBy("AudioDeviceBroker.mSetModeLock") 298 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") onSetBtActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType)299 void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) { 300 if (AudioService.DEBUG_DEVICES) { 301 Log.d(TAG, "onSetBtActiveDevice" 302 + " btDevice=" + btInfo.mDevice 303 + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile) 304 + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState)); 305 } 306 String address = btInfo.mDevice.getAddress(); 307 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 308 address = ""; 309 } 310 311 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent("BT connected:" 312 + " addr=" + address 313 + " profile=" + btInfo.mProfile 314 + " state=" + btInfo.mState 315 + " codec=" + AudioSystem.audioFormatToString(btInfo.mCodec))); 316 317 new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice") 318 .set(MediaMetrics.Property.STATUS, btInfo.mProfile) 319 .set(MediaMetrics.Property.DEVICE, 320 AudioSystem.getDeviceName(btInfo.mAudioSystemDevice)) 321 .set(MediaMetrics.Property.ADDRESS, address) 322 .set(MediaMetrics.Property.ENCODING, 323 AudioSystem.audioFormatToString(btInfo.mCodec)) 324 .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice") 325 .set(MediaMetrics.Property.STREAM_TYPE, 326 AudioSystem.streamToString(streamType)) 327 .set(MediaMetrics.Property.STATE, 328 btInfo.mState == BluetoothProfile.STATE_CONNECTED 329 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 330 .record(); 331 332 synchronized (mDevicesLock) { 333 final String key = DeviceInfo.makeDeviceListKey(btInfo.mAudioSystemDevice, address); 334 final DeviceInfo di = mConnectedDevices.get(key); 335 336 final boolean isConnected = di != null; 337 338 final boolean switchToUnavailable = isConnected 339 && btInfo.mState != BluetoothProfile.STATE_CONNECTED; 340 final boolean switchToAvailable = !isConnected 341 && btInfo.mState == BluetoothProfile.STATE_CONNECTED; 342 343 switch (btInfo.mProfile) { 344 case BluetoothProfile.A2DP_SINK: 345 if (switchToUnavailable) { 346 makeA2dpSrcUnavailable(address); 347 } else if (switchToAvailable) { 348 makeA2dpSrcAvailable(address); 349 } 350 break; 351 case BluetoothProfile.A2DP: 352 if (switchToUnavailable) { 353 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); 354 } else if (switchToAvailable) { 355 // device is not already connected 356 if (btInfo.mVolume != -1) { 357 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, 358 // convert index to internal representation in VolumeStreamState 359 btInfo.mVolume * 10, btInfo.mAudioSystemDevice, 360 "onSetBtActiveDevice"); 361 } 362 makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), 363 "onSetBtActiveDevice", btInfo.mCodec); 364 } 365 break; 366 case BluetoothProfile.HEARING_AID: 367 if (switchToUnavailable) { 368 makeHearingAidDeviceUnavailable(address); 369 } else if (switchToAvailable) { 370 makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), 371 streamType, "onSetBtActiveDevice"); 372 } 373 break; 374 case BluetoothProfile.LE_AUDIO: 375 case BluetoothProfile.LE_AUDIO_BROADCAST: 376 if (switchToUnavailable) { 377 makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); 378 } else if (switchToAvailable) { 379 makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), 380 streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, 381 btInfo.mAudioSystemDevice, 382 "onSetBtActiveDevice"); 383 } 384 break; 385 default: throw new IllegalArgumentException("Invalid profile " 386 + BluetoothProfile.getProfileName(btInfo.mProfile)); 387 } 388 } 389 } 390 391 392 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") onBluetoothA2dpDeviceConfigChange( @onNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event)393 /*package*/ void onBluetoothA2dpDeviceConfigChange( 394 @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { 395 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 396 + "onBluetoothA2dpDeviceConfigChange") 397 .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event)); 398 399 final BluetoothDevice btDevice = btInfo.getBtDevice(); 400 if (btDevice == null) { 401 mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); 402 return; 403 } 404 if (AudioService.DEBUG_DEVICES) { 405 Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); 406 } 407 int a2dpVolume = btInfo.getVolume(); 408 @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec(); 409 410 String address = btDevice.getAddress(); 411 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 412 address = ""; 413 } 414 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 415 "onBluetoothA2dpDeviceConfigChange addr=" + address 416 + " event=" + BtHelper.a2dpDeviceEventToString(event))); 417 418 synchronized (mDevicesLock) { 419 if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) { 420 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 421 "A2dp config change ignored (scheduled connection change)") 422 .printLog(TAG)); 423 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored") 424 .record(); 425 return; 426 } 427 final String key = DeviceInfo.makeDeviceListKey( 428 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 429 final DeviceInfo di = mConnectedDevices.get(key); 430 if (di == null) { 431 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); 432 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); 433 return; 434 } 435 436 mmi.set(MediaMetrics.Property.ADDRESS, address) 437 .set(MediaMetrics.Property.ENCODING, 438 AudioSystem.audioFormatToString(a2dpCodec)) 439 .set(MediaMetrics.Property.INDEX, a2dpVolume) 440 .set(MediaMetrics.Property.NAME, di.mDeviceName); 441 442 if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) { 443 // Device is connected 444 if (a2dpVolume != -1) { 445 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, 446 // convert index to internal representation in VolumeStreamState 447 a2dpVolume * 10, 448 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 449 "onBluetoothA2dpDeviceConfigChange"); 450 } 451 } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { 452 if (di.mDeviceCodecFormat != a2dpCodec) { 453 di.mDeviceCodecFormat = a2dpCodec; 454 mConnectedDevices.replace(key, di); 455 } 456 } 457 final int res = mAudioSystem.handleDeviceConfigChange( 458 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, 459 BtHelper.getName(btDevice), a2dpCodec); 460 461 if (res != AudioSystem.AUDIO_STATUS_OK) { 462 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 463 "APM handleDeviceConfigChange failed for A2DP device addr=" + address 464 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) 465 .printLog(TAG)); 466 467 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); 468 // force A2DP device disconnection in case of error so that AudioService state is 469 // consistent with audio policy manager state 470 setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice, 471 BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED, 472 musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); 473 } else { 474 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 475 "APM handleDeviceConfigChange success for A2DP device addr=" + address 476 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) 477 .printLog(TAG)); 478 } 479 } 480 mmi.record(); 481 } 482 onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec)483 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { 484 synchronized (mDevicesLock) { 485 makeA2dpDeviceUnavailableNow(address, a2dpCodec); 486 } 487 } 488 onMakeLeAudioDeviceUnavailableNow(String address, int device)489 /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) { 490 synchronized (mDevicesLock) { 491 makeLeAudioDeviceUnavailableNow(address, device); 492 } 493 } 494 onReportNewRoutes()495 /*package*/ void onReportNewRoutes() { 496 int n = mRoutesObservers.beginBroadcast(); 497 if (n > 0) { 498 new MediaMetrics.Item(mMetricsId + "onReportNewRoutes") 499 .set(MediaMetrics.Property.OBSERVERS, n) 500 .record(); 501 AudioRoutesInfo routes; 502 synchronized (mCurAudioRoutes) { 503 routes = new AudioRoutesInfo(mCurAudioRoutes); 504 } 505 while (n > 0) { 506 n--; 507 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); 508 try { 509 obs.dispatchAudioRoutesChanged(routes); 510 } catch (RemoteException e) { } 511 } 512 } 513 mRoutesObservers.finishBroadcast(); 514 mDeviceBroker.postObserveDevicesForAllStreams(); 515 } 516 517 /* package */ static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET; 518 static { 519 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>(); 520 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 521 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 522 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE); 523 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 524 } 525 onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs)526 /*package*/ void onSetWiredDeviceConnectionState( 527 AudioDeviceInventory.WiredDeviceConnectionState wdcs) { 528 int type = wdcs.mAttributes.getInternalType(); 529 530 AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); 531 532 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 533 + "onSetWiredDeviceConnectionState") 534 .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress()) 535 .set(MediaMetrics.Property.DEVICE, 536 AudioSystem.getDeviceName(type)) 537 .set(MediaMetrics.Property.STATE, 538 wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED 539 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED); 540 synchronized (mDevicesLock) { 541 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) 542 && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { 543 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, 544 "onSetWiredDeviceConnectionState state DISCONNECTED"); 545 } 546 547 if (!handleDeviceConnection(wdcs.mAttributes, 548 wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) { 549 // change of connection state failed, bailout 550 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") 551 .record(); 552 return; 553 } 554 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { 555 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { 556 mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/, 557 "onSetWiredDeviceConnectionState state not DISCONNECTED"); 558 } 559 mDeviceBroker.checkMusicActive(type, wdcs.mCaller); 560 } 561 if (type == AudioSystem.DEVICE_OUT_HDMI) { 562 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); 563 } 564 sendDeviceConnectionIntent(type, wdcs.mState, 565 wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName()); 566 updateAudioRoutes(type, wdcs.mState); 567 } 568 mmi.record(); 569 } 570 onToggleHdmi()571 /*package*/ void onToggleHdmi() { 572 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi") 573 .set(MediaMetrics.Property.DEVICE, 574 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI)); 575 synchronized (mDevicesLock) { 576 // Is HDMI connected? 577 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); 578 final DeviceInfo di = mConnectedDevices.get(key); 579 if (di == null) { 580 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); 581 mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record(); 582 return; 583 } 584 // Toggle HDMI to retrigger broadcast with proper formats. 585 setWiredDeviceConnectionState( 586 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), 587 AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect 588 setWiredDeviceConnectionState( 589 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), 590 AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect 591 } 592 mmi.record(); 593 } 594 onSaveSetPreferredDevices(int strategy, @NonNull List<AudioDeviceAttributes> devices)595 /*package*/ void onSaveSetPreferredDevices(int strategy, 596 @NonNull List<AudioDeviceAttributes> devices) { 597 mPreferredDevices.put(strategy, devices); 598 dispatchPreferredDevice(strategy, devices); 599 } 600 onSaveRemovePreferredDevices(int strategy)601 /*package*/ void onSaveRemovePreferredDevices(int strategy) { 602 mPreferredDevices.remove(strategy); 603 dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>()); 604 } 605 onSaveSetPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)606 /*package*/ void onSaveSetPreferredDevicesForCapturePreset( 607 int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { 608 mPreferredDevicesForCapturePreset.put(capturePreset, devices); 609 dispatchDevicesRoleForCapturePreset( 610 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 611 } 612 onSaveClearPreferredDevicesForCapturePreset(int capturePreset)613 /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) { 614 mPreferredDevicesForCapturePreset.remove(capturePreset); 615 dispatchDevicesRoleForCapturePreset( 616 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, 617 new ArrayList<AudioDeviceAttributes>()); 618 } 619 620 //------------------------------------------------------------ 621 // preferred device(s) 622 setPreferredDevicesForStrategySync(int strategy, @NonNull List<AudioDeviceAttributes> devices)623 /*package*/ int setPreferredDevicesForStrategySync(int strategy, 624 @NonNull List<AudioDeviceAttributes> devices) { 625 final long identity = Binder.clearCallingIdentity(); 626 627 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 628 "setPreferredDevicesForStrategySync, strategy: " + strategy 629 + " devices: " + devices)).printLog(TAG)); 630 final int status = mAudioSystem.setDevicesRoleForStrategy( 631 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 632 Binder.restoreCallingIdentity(identity); 633 634 if (status == AudioSystem.SUCCESS) { 635 mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices); 636 } 637 return status; 638 } 639 removePreferredDevicesForStrategySync(int strategy)640 /*package*/ int removePreferredDevicesForStrategySync(int strategy) { 641 final long identity = Binder.clearCallingIdentity(); 642 643 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 644 "removePreferredDevicesForStrategySync, strategy: " 645 + strategy)).printLog(TAG)); 646 647 final int status = mAudioSystem.removeDevicesRoleForStrategy( 648 strategy, AudioSystem.DEVICE_ROLE_PREFERRED); 649 Binder.restoreCallingIdentity(identity); 650 651 if (status == AudioSystem.SUCCESS) { 652 mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy); 653 } 654 return status; 655 } 656 registerStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)657 /*package*/ void registerStrategyPreferredDevicesDispatcher( 658 @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { 659 mPrefDevDispatchers.register(dispatcher); 660 } 661 unregisterStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)662 /*package*/ void unregisterStrategyPreferredDevicesDispatcher( 663 @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { 664 mPrefDevDispatchers.unregister(dispatcher); 665 } 666 setPreferredDevicesForCapturePresetSync( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)667 /*package*/ int setPreferredDevicesForCapturePresetSync( 668 int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { 669 final long identity = Binder.clearCallingIdentity(); 670 final int status = mAudioSystem.setDevicesRoleForCapturePreset( 671 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 672 Binder.restoreCallingIdentity(identity); 673 674 if (status == AudioSystem.SUCCESS) { 675 mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices); 676 } 677 return status; 678 } 679 clearPreferredDevicesForCapturePresetSync(int capturePreset)680 /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { 681 final long identity = Binder.clearCallingIdentity(); 682 final int status = mAudioSystem.clearDevicesRoleForCapturePreset( 683 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED); 684 Binder.restoreCallingIdentity(identity); 685 686 if (status == AudioSystem.SUCCESS) { 687 mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset); 688 } 689 return status; 690 } 691 registerCapturePresetDevicesRoleDispatcher( @onNull ICapturePresetDevicesRoleDispatcher dispatcher)692 /*package*/ void registerCapturePresetDevicesRoleDispatcher( 693 @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { 694 mDevRoleCapturePresetDispatchers.register(dispatcher); 695 } 696 unregisterCapturePresetDevicesRoleDispatcher( @onNull ICapturePresetDevicesRoleDispatcher dispatcher)697 /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( 698 @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { 699 mDevRoleCapturePresetDispatchers.unregister(dispatcher); 700 } 701 702 //----------------------------------------------------------------------- 703 704 /** 705 * Check if a device is in the list of connected devices 706 * @param device the device whose connection state is queried 707 * @return true if connected 708 */ 709 // called with AudioDeviceBroker.mDeviceStateLock lock held isDeviceConnected(@onNull AudioDeviceAttributes device)710 public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) { 711 final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), 712 device.getAddress()); 713 synchronized (mDevicesLock) { 714 return (mConnectedDevices.get(key) != null); 715 } 716 } 717 718 /** 719 * Implements the communication with AudioSystem to (dis)connect a device in the native layers 720 * @param attributes the attributes of the device 721 * @param connect true if connection 722 * @param isForTesting if true, not calling AudioSystem for the connection as this is 723 * just for testing 724 * @return false if an error was reported by AudioSystem 725 */ handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, boolean isForTesting)726 /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, 727 boolean isForTesting) { 728 int device = attributes.getInternalType(); 729 String address = attributes.getAddress(); 730 String deviceName = attributes.getName(); 731 if (AudioService.DEBUG_DEVICES) { 732 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" 733 + Integer.toHexString(device) + " address:" + address 734 + " name:" + deviceName + ")"); 735 } 736 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection") 737 .set(MediaMetrics.Property.ADDRESS, address) 738 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 739 .set(MediaMetrics.Property.MODE, connect 740 ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) 741 .set(MediaMetrics.Property.NAME, deviceName); 742 synchronized (mDevicesLock) { 743 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); 744 if (AudioService.DEBUG_DEVICES) { 745 Slog.i(TAG, "deviceKey:" + deviceKey); 746 } 747 DeviceInfo di = mConnectedDevices.get(deviceKey); 748 boolean isConnected = di != null; 749 if (AudioService.DEBUG_DEVICES) { 750 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); 751 } 752 if (connect && !isConnected) { 753 final int res; 754 if (isForTesting) { 755 res = AudioSystem.AUDIO_STATUS_OK; 756 } else { 757 res = mAudioSystem.setDeviceConnectionState(attributes, 758 AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); 759 } 760 if (res != AudioSystem.AUDIO_STATUS_OK) { 761 final String reason = "not connecting device 0x" + Integer.toHexString(device) 762 + " due to command error " + res; 763 Slog.e(TAG, reason); 764 mmi.set(MediaMetrics.Property.EARLY_RETURN, reason) 765 .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED) 766 .record(); 767 return false; 768 } 769 mConnectedDevices.put(deviceKey, new DeviceInfo( 770 device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 771 mDeviceBroker.postAccessoryPlugMediaUnmute(device); 772 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); 773 return true; 774 } else if (!connect && isConnected) { 775 mAudioSystem.setDeviceConnectionState(attributes, 776 AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); 777 // always remove even if disconnection failed 778 mConnectedDevices.remove(deviceKey); 779 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); 780 return true; 781 } 782 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey 783 + ", deviceSpec=" + di + ", connect=" + connect); 784 } 785 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); 786 return false; 787 } 788 789 disconnectA2dp()790 private void disconnectA2dp() { 791 synchronized (mDevicesLock) { 792 final ArraySet<String> toRemove = new ArraySet<>(); 793 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices 794 mConnectedDevices.values().forEach(deviceInfo -> { 795 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { 796 toRemove.add(deviceInfo.mDeviceAddress); 797 } 798 }); 799 new MediaMetrics.Item(mMetricsId + "disconnectA2dp") 800 .record(); 801 if (toRemove.size() > 0) { 802 final int delay = checkSendBecomingNoisyIntentInt( 803 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 804 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); 805 toRemove.stream().forEach(deviceAddress -> 806 makeA2dpDeviceUnavailableLater(deviceAddress, delay) 807 ); 808 } 809 } 810 } 811 disconnectA2dpSink()812 private void disconnectA2dpSink() { 813 synchronized (mDevicesLock) { 814 final ArraySet<String> toRemove = new ArraySet<>(); 815 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices 816 mConnectedDevices.values().forEach(deviceInfo -> { 817 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { 818 toRemove.add(deviceInfo.mDeviceAddress); 819 } 820 }); 821 new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink") 822 .record(); 823 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); 824 } 825 } 826 disconnectHearingAid()827 private void disconnectHearingAid() { 828 synchronized (mDevicesLock) { 829 final ArraySet<String> toRemove = new ArraySet<>(); 830 // Disconnect ALL DEVICE_OUT_HEARING_AID devices 831 mConnectedDevices.values().forEach(deviceInfo -> { 832 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { 833 toRemove.add(deviceInfo.mDeviceAddress); 834 } 835 }); 836 new MediaMetrics.Item(mMetricsId + "disconnectHearingAid") 837 .record(); 838 if (toRemove.size() > 0) { 839 final int delay = checkSendBecomingNoisyIntentInt( 840 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); 841 toRemove.stream().forEach(deviceAddress -> 842 // TODO delay not used? 843 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) 844 ); 845 } 846 } 847 } 848 onBtProfileDisconnected(int profile)849 /*package*/ synchronized void onBtProfileDisconnected(int profile) { 850 switch (profile) { 851 case BluetoothProfile.A2DP: 852 disconnectA2dp(); 853 break; 854 case BluetoothProfile.A2DP_SINK: 855 disconnectA2dpSink(); 856 break; 857 case BluetoothProfile.HEARING_AID: 858 disconnectHearingAid(); 859 break; 860 case BluetoothProfile.LE_AUDIO: 861 disconnectLeAudioUnicast(); 862 break; 863 case BluetoothProfile.LE_AUDIO_BROADCAST: 864 disconnectLeAudioBroadcast(); 865 break; 866 default: 867 // Not a valid profile to disconnect 868 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " 869 + BluetoothProfile.getProfileName(profile)); 870 break; 871 } 872 } 873 disconnectLeAudio(int device)874 /*package*/ void disconnectLeAudio(int device) { 875 if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET 876 && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { 877 Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device); 878 return; 879 } 880 881 synchronized (mDevicesLock) { 882 final ArraySet<String> toRemove = new ArraySet<>(); 883 // Disconnect ALL DEVICE_OUT_BLE_HEADSET or DEVICE_OUT_BLE_BROADCAST devices 884 mConnectedDevices.values().forEach(deviceInfo -> { 885 if (deviceInfo.mDeviceType == device) { 886 toRemove.add(deviceInfo.mDeviceAddress); 887 } 888 }); 889 new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") 890 .record(); 891 if (toRemove.size() > 0) { 892 final int delay = checkSendBecomingNoisyIntentInt(device, 893 AudioService.CONNECTION_STATE_DISCONNECTED, 894 AudioSystem.DEVICE_NONE); 895 toRemove.stream().forEach(deviceAddress -> 896 makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay) 897 ); 898 } 899 } 900 } 901 disconnectLeAudioUnicast()902 /*package*/ void disconnectLeAudioUnicast() { 903 disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_HEADSET); 904 } 905 disconnectLeAudioBroadcast()906 /*package*/ void disconnectLeAudioBroadcast() { 907 disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST); 908 } 909 910 // must be called before removing the device from mConnectedDevices 911 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 912 // from AudioSystem checkSendBecomingNoisyIntent(int device, @AudioService.ConnectionState int state, int musicDevice)913 /*package*/ int checkSendBecomingNoisyIntent(int device, 914 @AudioService.ConnectionState int state, int musicDevice) { 915 synchronized (mDevicesLock) { 916 return checkSendBecomingNoisyIntentInt(device, state, musicDevice); 917 } 918 } 919 startWatchingRoutes(IAudioRoutesObserver observer)920 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { 921 synchronized (mCurAudioRoutes) { 922 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); 923 mRoutesObservers.register(observer); 924 return routes; 925 } 926 } 927 getCurAudioRoutes()928 /*package*/ AudioRoutesInfo getCurAudioRoutes() { 929 return mCurAudioRoutes; 930 } 931 932 // only public for mocking/spying 933 @GuardedBy("AudioDeviceBroker.mDeviceStateLock") 934 @VisibleForTesting setBluetoothActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo info)935 public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) { 936 int delay; 937 synchronized (mDevicesLock) { 938 if (!info.mSupprNoisy 939 && (((info.mProfile == BluetoothProfile.LE_AUDIO 940 || info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) 941 && info.mIsLeOutput) 942 || info.mProfile == BluetoothProfile.HEARING_AID 943 || info.mProfile == BluetoothProfile.A2DP)) { 944 @AudioService.ConnectionState int asState = 945 (info.mState == BluetoothProfile.STATE_CONNECTED) 946 ? AudioService.CONNECTION_STATE_CONNECTED 947 : AudioService.CONNECTION_STATE_DISCONNECTED; 948 delay = checkSendBecomingNoisyIntentInt(info.mAudioSystemDevice, asState, 949 info.mMusicDevice); 950 } else { 951 delay = 0; 952 } 953 954 if (AudioService.DEBUG_DEVICES) { 955 Log.i(TAG, "setBluetoothActiveDevice device: " + info.mDevice 956 + " profile: " + BluetoothProfile.getProfileName(info.mProfile) 957 + " state: " + BluetoothProfile.getConnectionStateName(info.mState) 958 + " delay(ms): " + delay 959 + " codec:" + Integer.toHexString(info.mCodec) 960 + " suppressNoisyIntent: " + info.mSupprNoisy); 961 } 962 mDeviceBroker.postBluetoothActiveDevice(info, delay); 963 if (info.mProfile == BluetoothProfile.HEARING_AID 964 && info.mState == BluetoothProfile.STATE_CONNECTED) { 965 mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE, 966 "HEARING_AID set to CONNECTED"); 967 } 968 } 969 return delay; 970 } 971 setWiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)972 /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes, 973 @AudioService.ConnectionState int state, String caller) { 974 synchronized (mDevicesLock) { 975 int delay = checkSendBecomingNoisyIntentInt( 976 attributes.getInternalType(), state, AudioSystem.DEVICE_NONE); 977 mDeviceBroker.postSetWiredDeviceConnectionState( 978 new WiredDeviceConnectionState(attributes, state, caller), delay); 979 return delay; 980 } 981 } 982 setTestDeviceConnectionState(@onNull AudioDeviceAttributes device, @AudioService.ConnectionState int state)983 /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, 984 @AudioService.ConnectionState int state) { 985 final WiredDeviceConnectionState connection = new WiredDeviceConnectionState( 986 device, state, "com.android.server.audio"); 987 connection.mForTest = true; 988 onSetWiredDeviceConnectionState(connection); 989 } 990 991 //------------------------------------------------------------------- 992 // Internal utilities 993 994 @GuardedBy("mDevicesLock") makeA2dpDeviceAvailable(String address, String name, String eventSource, int a2dpCodec)995 private void makeA2dpDeviceAvailable(String address, String name, String eventSource, 996 int a2dpCodec) { 997 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in 998 // audio policy manager 999 mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); 1000 // at this point there could be another A2DP device already connected in APM, but it 1001 // doesn't matter as this new one will overwrite the previous one 1002 final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1003 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), 1004 AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); 1005 1006 // TODO: log in MediaMetrics once distinction between connection failure and 1007 // double connection is made. 1008 if (res != AudioSystem.AUDIO_STATUS_OK) { 1009 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1010 "APM failed to make available A2DP device addr=" + address 1011 + " error=" + res).printLog(TAG)); 1012 // TODO: connection failed, stop here 1013 // TODO: return; 1014 } else { 1015 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1016 "A2DP device addr=" + address + " now available").printLog(TAG)); 1017 } 1018 1019 // Reset A2DP suspend state each time a new sink is connected 1020 mAudioSystem.setParameters("A2dpSuspended=false"); 1021 1022 // The convention for head tracking sensors associated with A2DP devices is to 1023 // use a UUID derived from the MAC address as follows: 1024 // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address 1025 UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( 1026 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); 1027 final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, 1028 address, a2dpCodec, sensorUuid); 1029 final String diKey = di.getKey(); 1030 mConnectedDevices.put(diKey, di); 1031 // on a connection always overwrite the device seen by AudioPolicy, see comment above when 1032 // calling AudioSystem 1033 mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey); 1034 1035 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 1036 setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); 1037 } 1038 1039 @GuardedBy("mDevicesLock") makeA2dpDeviceUnavailableNow(String address, int a2dpCodec)1040 private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { 1041 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address) 1042 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec)) 1043 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow"); 1044 1045 if (address == null) { 1046 mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record(); 1047 return; 1048 } 1049 final String deviceToRemoveKey = 1050 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 1051 1052 mConnectedDevices.remove(deviceToRemoveKey); 1053 if (!deviceToRemoveKey 1054 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { 1055 // removing A2DP device not currently used by AudioPolicy, log but don't act on it 1056 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 1057 "A2DP device " + address + " made unavailable, was not used")).printLog(TAG)); 1058 mmi.set(MediaMetrics.Property.EARLY_RETURN, 1059 "A2DP device made unavailable, was not used") 1060 .record(); 1061 return; 1062 } 1063 1064 // device to remove was visible by APM, update APM 1065 mDeviceBroker.clearAvrcpAbsoluteVolumeSupported(); 1066 final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1067 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), 1068 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec); 1069 1070 if (res != AudioSystem.AUDIO_STATUS_OK) { 1071 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1072 "APM failed to make unavailable A2DP device addr=" + address 1073 + " error=" + res).printLog(TAG)); 1074 // TODO: failed to disconnect, stop here 1075 // TODO: return; 1076 } else { 1077 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 1078 "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); 1079 } 1080 mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 1081 // Remove A2DP routes as well 1082 setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); 1083 mmi.record(); 1084 } 1085 1086 @GuardedBy("mDevicesLock") makeA2dpDeviceUnavailableLater(String address, int delayMs)1087 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { 1088 // prevent any activity on the A2DP audio output to avoid unwanted 1089 // reconnection of the sink. 1090 mAudioSystem.setParameters("A2dpSuspended=true"); 1091 // retrieve DeviceInfo before removing device 1092 final String deviceKey = 1093 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 1094 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); 1095 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : 1096 AudioSystem.AUDIO_FORMAT_DEFAULT; 1097 // the device will be made unavailable later, so consider it disconnected right away 1098 mConnectedDevices.remove(deviceKey); 1099 // send the delayed message to make the device unavailable later 1100 mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs); 1101 } 1102 1103 1104 @GuardedBy("mDevicesLock") makeA2dpSrcAvailable(String address)1105 private void makeA2dpSrcAvailable(String address) { 1106 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1107 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 1108 AudioSystem.DEVICE_STATE_AVAILABLE, 1109 AudioSystem.AUDIO_FORMAT_DEFAULT); 1110 mConnectedDevices.put( 1111 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 1112 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", 1113 address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 1114 } 1115 1116 @GuardedBy("mDevicesLock") makeA2dpSrcUnavailable(String address)1117 private void makeA2dpSrcUnavailable(String address) { 1118 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1119 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 1120 AudioSystem.DEVICE_STATE_UNAVAILABLE, 1121 AudioSystem.AUDIO_FORMAT_DEFAULT); 1122 mConnectedDevices.remove( 1123 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); 1124 } 1125 1126 @GuardedBy("mDevicesLock") makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource)1127 private void makeHearingAidDeviceAvailable( 1128 String address, String name, int streamType, String eventSource) { 1129 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, 1130 AudioSystem.DEVICE_OUT_HEARING_AID); 1131 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); 1132 1133 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1134 AudioSystem.DEVICE_OUT_HEARING_AID, address, name), 1135 AudioSystem.DEVICE_STATE_AVAILABLE, 1136 AudioSystem.AUDIO_FORMAT_DEFAULT); 1137 mConnectedDevices.put( 1138 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), 1139 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, 1140 address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 1141 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); 1142 mDeviceBroker.postApplyVolumeOnDevice(streamType, 1143 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); 1144 setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); 1145 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") 1146 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 1147 .set(MediaMetrics.Property.DEVICE, 1148 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 1149 .set(MediaMetrics.Property.NAME, name) 1150 .set(MediaMetrics.Property.STREAM_TYPE, 1151 AudioSystem.streamToString(streamType)) 1152 .record(); 1153 } 1154 1155 @GuardedBy("mDevicesLock") makeHearingAidDeviceUnavailable(String address)1156 private void makeHearingAidDeviceUnavailable(String address) { 1157 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1158 AudioSystem.DEVICE_OUT_HEARING_AID, address), 1159 AudioSystem.DEVICE_STATE_UNAVAILABLE, 1160 AudioSystem.AUDIO_FORMAT_DEFAULT); 1161 mConnectedDevices.remove( 1162 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); 1163 // Remove Hearing Aid routes as well 1164 setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); 1165 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable") 1166 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 1167 .set(MediaMetrics.Property.DEVICE, 1168 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 1169 .record(); 1170 } 1171 1172 /** 1173 * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected. 1174 * Visibility by APM plays no role 1175 * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise. 1176 */ isHearingAidConnected()1177 boolean isHearingAidConnected() { 1178 synchronized (mDevicesLock) { 1179 for (DeviceInfo di : mConnectedDevices.values()) { 1180 if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { 1181 return true; 1182 } 1183 } 1184 return false; 1185 } 1186 } 1187 1188 @GuardedBy("mDevicesLock") makeLeAudioDeviceAvailable(String address, String name, int streamType, int volumeIndex, int device, String eventSource)1189 private void makeLeAudioDeviceAvailable(String address, String name, int streamType, 1190 int volumeIndex, int device, String eventSource) { 1191 if (device != AudioSystem.DEVICE_NONE) { 1192 /* Audio Policy sees Le Audio similar to A2DP. Let's make sure 1193 * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set 1194 */ 1195 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); 1196 1197 final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1198 device, address, name), 1199 AudioSystem.DEVICE_STATE_AVAILABLE, 1200 AudioSystem.AUDIO_FORMAT_DEFAULT); 1201 if (res != AudioSystem.AUDIO_STATUS_OK) { 1202 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1203 "APM failed to make available LE Audio device addr=" + address 1204 + " error=" + res).printLog(TAG)); 1205 // TODO: connection failed, stop here 1206 // TODO: return; 1207 } else { 1208 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1209 "LE Audio device addr=" + address + " now available").printLog(TAG)); 1210 } 1211 1212 mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), 1213 new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); 1214 mDeviceBroker.postAccessoryPlugMediaUnmute(device); 1215 setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); 1216 } 1217 1218 if (streamType == AudioSystem.STREAM_DEFAULT) { 1219 // No need to update volume for input devices 1220 return; 1221 } 1222 1223 final int leAudioVolIndex = (volumeIndex == -1) 1224 ? mDeviceBroker.getVssVolumeForDevice(streamType, device) 1225 : volumeIndex; 1226 final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); 1227 mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); 1228 mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); 1229 } 1230 1231 @GuardedBy("mDevicesLock") makeLeAudioDeviceUnavailableNow(String address, int device)1232 private void makeLeAudioDeviceUnavailableNow(String address, int device) { 1233 if (device != AudioSystem.DEVICE_NONE) { 1234 final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1235 device, address), 1236 AudioSystem.DEVICE_STATE_UNAVAILABLE, 1237 AudioSystem.AUDIO_FORMAT_DEFAULT); 1238 1239 if (res != AudioSystem.AUDIO_STATUS_OK) { 1240 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( 1241 "APM failed to make unavailable LE Audio device addr=" + address 1242 + " error=" + res).printLog(TAG)); 1243 // TODO: failed to disconnect, stop here 1244 // TODO: return; 1245 } else { 1246 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 1247 "LE Audio device addr=" + address + " made unavailable")).printLog(TAG)); 1248 } 1249 mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); 1250 } 1251 1252 setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); 1253 } 1254 1255 @GuardedBy("mDevicesLock") makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs)1256 private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { 1257 // the device will be made unavailable later, so consider it disconnected right away 1258 mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); 1259 // send the delayed message to make the device unavailable later 1260 mDeviceBroker.setLeAudioTimeout(address, device, delayMs); 1261 } 1262 1263 @GuardedBy("mDevicesLock") setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp)1264 private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { 1265 synchronized (mCurAudioRoutes) { 1266 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { 1267 return; 1268 } 1269 if (name != null || !isCurrentDeviceConnected()) { 1270 mCurAudioRoutes.bluetoothName = name; 1271 mDeviceBroker.postReportNewRoutes(fromA2dp); 1272 } 1273 } 1274 } 1275 1276 @GuardedBy("mDevicesLock") isCurrentDeviceConnected()1277 private boolean isCurrentDeviceConnected() { 1278 return mConnectedDevices.values().stream().anyMatch(deviceInfo -> 1279 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); 1280 } 1281 1282 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only 1283 // sent if: 1284 // - none of these devices are connected anymore after one is disconnected AND 1285 // - the device being disconnected is actually used for music. 1286 // Access synchronized on mConnectedDevices 1287 private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET; 1288 static { 1289 BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>(); 1290 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 1291 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 1292 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI); 1293 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); 1294 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE); 1295 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); 1296 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET); 1297 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST); 1298 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); 1299 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 1300 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET); 1301 } 1302 1303 // must be called before removing the device from mConnectedDevices 1304 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 1305 // from AudioSystem 1306 @GuardedBy("mDevicesLock") checkSendBecomingNoisyIntentInt(int device, @AudioService.ConnectionState int state, int musicDevice)1307 private int checkSendBecomingNoisyIntentInt(int device, 1308 @AudioService.ConnectionState int state, int musicDevice) { 1309 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 1310 + "checkSendBecomingNoisyIntentInt") 1311 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 1312 .set(MediaMetrics.Property.STATE, 1313 state == AudioService.CONNECTION_STATE_CONNECTED 1314 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED); 1315 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { 1316 Log.i(TAG, "not sending NOISY: state=" + state); 1317 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1318 return 0; 1319 } 1320 if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) { 1321 Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device) 1322 + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET); 1323 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1324 return 0; 1325 } 1326 int delay = 0; 1327 Set<Integer> devices = new HashSet<>(); 1328 for (DeviceInfo di : mConnectedDevices.values()) { 1329 if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0) 1330 && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) { 1331 devices.add(di.mDeviceType); 1332 Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType)); 1333 } 1334 } 1335 if (musicDevice == AudioSystem.DEVICE_NONE) { 1336 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); 1337 Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x" 1338 + Integer.toHexString(musicDevice)); 1339 } 1340 1341 // always ignore condition on device being actually used for music when in communication 1342 // because music routing is altered in this case. 1343 // also checks whether media routing if affected by a dynamic policy or mirroring 1344 final boolean inCommunication = mDeviceBroker.isInCommunication(); 1345 final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device); 1346 final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy(); 1347 if (((device == musicDevice) || inCommunication) 1348 && singleAudioDeviceType 1349 && !hasMediaDynamicPolicy 1350 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) { 1351 if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/) 1352 && !mDeviceBroker.hasAudioFocusUsers()) { 1353 // no media playback, not a "becoming noisy" situation, otherwise it could cause 1354 // the pausing of some apps that are playing remotely 1355 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( 1356 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); 1357 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 1358 return 0; 1359 } 1360 mDeviceBroker.postBroadcastBecomingNoisy(); 1361 delay = AudioService.BECOMING_NOISY_DELAY_MS; 1362 } else { 1363 Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device) 1364 + " musicDevice:0x" + Integer.toHexString(musicDevice) 1365 + " inComm:" + inCommunication 1366 + " mediaPolicy:" + hasMediaDynamicPolicy 1367 + " singleDevice:" + singleAudioDeviceType); 1368 } 1369 1370 mmi.set(MediaMetrics.Property.DELAY_MS, delay).record(); 1371 return delay; 1372 } 1373 1374 // Intent "extra" data keys. 1375 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; 1376 private static final String CONNECT_INTENT_KEY_STATE = "state"; 1377 private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; 1378 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; 1379 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; 1380 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; 1381 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; 1382 sendDeviceConnectionIntent(int device, int state, String address, String deviceName)1383 private void sendDeviceConnectionIntent(int device, int state, String address, 1384 String deviceName) { 1385 if (AudioService.DEBUG_DEVICES) { 1386 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) 1387 + " state:0x" + Integer.toHexString(state) + " address:" + address 1388 + " name:" + deviceName + ");"); 1389 } 1390 Intent intent = new Intent(); 1391 1392 switch(device) { 1393 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 1394 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1395 intent.putExtra("microphone", 1); 1396 break; 1397 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 1398 case AudioSystem.DEVICE_OUT_LINE: 1399 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1400 intent.putExtra("microphone", 0); 1401 break; 1402 case AudioSystem.DEVICE_OUT_USB_HEADSET: 1403 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1404 intent.putExtra("microphone", 1405 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") 1406 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); 1407 break; 1408 case AudioSystem.DEVICE_IN_USB_HEADSET: 1409 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") 1410 == AudioSystem.DEVICE_STATE_AVAILABLE) { 1411 intent.setAction(Intent.ACTION_HEADSET_PLUG); 1412 intent.putExtra("microphone", 1); 1413 } else { 1414 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing 1415 return; 1416 } 1417 break; 1418 case AudioSystem.DEVICE_OUT_HDMI: 1419 case AudioSystem.DEVICE_OUT_HDMI_ARC: 1420 case AudioSystem.DEVICE_OUT_HDMI_EARC: 1421 configureHdmiPlugIntent(intent, state); 1422 break; 1423 } 1424 1425 if (intent.getAction() == null) { 1426 return; 1427 } 1428 1429 intent.putExtra(CONNECT_INTENT_KEY_STATE, state); 1430 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); 1431 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); 1432 1433 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 1434 1435 final long ident = Binder.clearCallingIdentity(); 1436 try { 1437 mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent); 1438 } finally { 1439 Binder.restoreCallingIdentity(ident); 1440 } 1441 } 1442 updateAudioRoutes(int device, int state)1443 private void updateAudioRoutes(int device, int state) { 1444 int connType = 0; 1445 1446 switch (device) { 1447 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 1448 connType = AudioRoutesInfo.MAIN_HEADSET; 1449 break; 1450 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 1451 case AudioSystem.DEVICE_OUT_LINE: 1452 connType = AudioRoutesInfo.MAIN_HEADPHONES; 1453 break; 1454 case AudioSystem.DEVICE_OUT_HDMI: 1455 case AudioSystem.DEVICE_OUT_HDMI_ARC: 1456 case AudioSystem.DEVICE_OUT_HDMI_EARC: 1457 connType = AudioRoutesInfo.MAIN_HDMI; 1458 break; 1459 case AudioSystem.DEVICE_OUT_USB_DEVICE: 1460 case AudioSystem.DEVICE_OUT_USB_HEADSET: 1461 connType = AudioRoutesInfo.MAIN_USB; 1462 break; 1463 case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET: 1464 connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; 1465 break; 1466 } 1467 1468 synchronized (mCurAudioRoutes) { 1469 if (connType == 0) { 1470 return; 1471 } 1472 int newConn = mCurAudioRoutes.mainType; 1473 if (state != 0) { 1474 newConn |= connType; 1475 } else { 1476 newConn &= ~connType; 1477 } 1478 if (newConn != mCurAudioRoutes.mainType) { 1479 mCurAudioRoutes.mainType = newConn; 1480 mDeviceBroker.postReportNewRoutes(false /*fromA2dp*/); 1481 } 1482 } 1483 } 1484 configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state)1485 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { 1486 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); 1487 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); 1488 if (state != AudioService.CONNECTION_STATE_CONNECTED) { 1489 return; 1490 } 1491 ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); 1492 int[] portGeneration = new int[1]; 1493 int status = AudioSystem.listAudioPorts(ports, portGeneration); 1494 if (status != AudioManager.SUCCESS) { 1495 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); 1496 return; 1497 } 1498 for (AudioPort port : ports) { 1499 if (!(port instanceof AudioDevicePort)) { 1500 continue; 1501 } 1502 final AudioDevicePort devicePort = (AudioDevicePort) port; 1503 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI 1504 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC 1505 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_EARC) { 1506 continue; 1507 } 1508 // found an HDMI port: format the list of supported encodings 1509 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); 1510 if (formats.length > 0) { 1511 ArrayList<Integer> encodingList = new ArrayList(1); 1512 for (int format : formats) { 1513 // a format in the list can be 0, skip it 1514 if (format != AudioFormat.ENCODING_INVALID) { 1515 encodingList.add(format); 1516 } 1517 } 1518 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); 1519 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); 1520 } 1521 // find the maximum supported number of channels 1522 int maxChannels = 0; 1523 for (int mask : devicePort.channelMasks()) { 1524 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); 1525 if (channelCount > maxChannels) { 1526 maxChannels = channelCount; 1527 } 1528 } 1529 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); 1530 } 1531 } 1532 dispatchPreferredDevice(int strategy, @NonNull List<AudioDeviceAttributes> devices)1533 private void dispatchPreferredDevice(int strategy, 1534 @NonNull List<AudioDeviceAttributes> devices) { 1535 final int nbDispatchers = mPrefDevDispatchers.beginBroadcast(); 1536 for (int i = 0; i < nbDispatchers; i++) { 1537 try { 1538 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged( 1539 strategy, devices); 1540 } catch (RemoteException e) { 1541 } 1542 } 1543 mPrefDevDispatchers.finishBroadcast(); 1544 } 1545 dispatchDevicesRoleForCapturePreset( int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1546 private void dispatchDevicesRoleForCapturePreset( 1547 int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { 1548 final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast(); 1549 for (int i = 0; i < nbDispatchers; ++i) { 1550 try { 1551 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged( 1552 capturePreset, role, devices); 1553 } catch (RemoteException e) { 1554 } 1555 } 1556 mDevRoleCapturePresetDispatchers.finishBroadcast(); 1557 } 1558 getDeviceSensorUuid(AudioDeviceAttributes device)1559 @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { 1560 final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), 1561 device.getAddress()); 1562 synchronized (mDevicesLock) { 1563 DeviceInfo di = mConnectedDevices.get(key); 1564 if (di == null) { 1565 return null; 1566 } 1567 return di.mSensorUuid; 1568 } 1569 } 1570 getDeviceOfType(int type)1571 /* package */ AudioDeviceAttributes getDeviceOfType(int type) { 1572 synchronized (mDevicesLock) { 1573 for (DeviceInfo di : mConnectedDevices.values()) { 1574 if (di.mDeviceType == type) { 1575 return new AudioDeviceAttributes( 1576 di.mDeviceType, di.mDeviceAddress, di.mDeviceName); 1577 } 1578 } 1579 } 1580 return null; 1581 } 1582 1583 //---------------------------------------------------------- 1584 // For tests only 1585 1586 /** 1587 * Check if device is in the list of connected devices 1588 * @param device 1589 * @return true if connected 1590 */ 1591 @VisibleForTesting isA2dpDeviceConnected(@onNull BluetoothDevice device)1592 public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { 1593 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 1594 device.getAddress()); 1595 synchronized (mDevicesLock) { 1596 return (mConnectedDevices.get(key) != null); 1597 } 1598 } 1599 } 1600