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