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