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 static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET; 19 import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET; 20 import static android.media.AudioSystem.isBluetoothDevice; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.Intent; 28 import android.media.AudioDeviceAttributes; 29 import android.media.AudioDeviceInfo; 30 import android.media.AudioDevicePort; 31 import android.media.AudioFormat; 32 import android.media.AudioManager; 33 import android.media.AudioPort; 34 import android.media.AudioRoutesInfo; 35 import android.media.AudioSystem; 36 import android.media.IAudioRoutesObserver; 37 import android.media.ICapturePresetDevicesRoleDispatcher; 38 import android.media.IStrategyNonDefaultDevicesDispatcher; 39 import android.media.IStrategyPreferredDevicesDispatcher; 40 import android.media.MediaMetrics; 41 import android.media.MediaRecorder.AudioSource; 42 import android.media.audiopolicy.AudioProductStrategy; 43 import android.media.permission.ClearCallingIdentityContext; 44 import android.media.permission.SafeCloseable; 45 import android.os.Binder; 46 import android.os.Bundle; 47 import android.os.RemoteCallbackList; 48 import android.os.RemoteException; 49 import android.os.SystemProperties; 50 import android.text.TextUtils; 51 import android.util.ArrayMap; 52 import android.util.ArraySet; 53 import android.util.Log; 54 import android.util.Pair; 55 import android.util.Slog; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.annotations.VisibleForTesting; 59 import com.android.server.utils.EventLogger; 60 61 import com.google.android.collect.Sets; 62 63 import java.io.PrintWriter; 64 import java.util.ArrayList; 65 import java.util.Arrays; 66 import java.util.Collection; 67 import java.util.HashMap; 68 import java.util.HashSet; 69 import java.util.Iterator; 70 import java.util.LinkedHashMap; 71 import java.util.List; 72 import java.util.Map.Entry; 73 import java.util.Objects; 74 import java.util.Set; 75 import java.util.UUID; 76 import java.util.stream.Stream; 77 78 /** 79 * Class to manage the inventory of all connected devices. 80 * This class is thread-safe. 81 * (non final for mocking/spying) 82 */ 83 public class AudioDeviceInventory { 84 85 private static final String TAG = "AS.AudioDeviceInventory"; 86 87 private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; 88 private static final String SETTING_DEVICE_SEPARATOR = "\\|"; 89 90 // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices 91 private final Object mDevicesLock = new Object(); 92 93 //Audio Analytics ids. 94 private static final String mMetricsId = "audio.device."; 95 96 private final Object mDeviceInventoryLock = new Object(); 97 98 @GuardedBy("mDeviceInventoryLock") 99 private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); 100 getImmutableDeviceInventory()101 Collection<AdiDeviceState> getImmutableDeviceInventory() { 102 synchronized (mDeviceInventoryLock) { 103 return mDeviceInventory.values(); 104 } 105 } 106 107 /** 108 * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching 109 * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. 110 * @param deviceState the device to update 111 */ addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState)112 void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { 113 synchronized (mDeviceInventoryLock) { 114 mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { 115 oldState.setHasHeadTracker(newState.hasHeadTracker()); 116 oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled()); 117 oldState.setSAEnabled(newState.isSAEnabled()); 118 return oldState; 119 }); 120 } 121 } 122 123 /** 124 * Adds a new AdiDeviceState or updates the audio device cateogory of the matching 125 * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. 126 * @param deviceState the device to update 127 */ addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState)128 void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { 129 synchronized (mDeviceInventoryLock) { 130 mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { 131 oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory()); 132 return oldState; 133 }); 134 } 135 } 136 137 /** 138 * Finds the BT device that matches the passed {@code address}. Currently, this method only 139 * returns a valid device for A2DP and BLE devices. 140 * 141 * @param address MAC address of BT device 142 * @param isBle true if the device is BLE, false for A2DP 143 * @return the found {@link AdiDeviceState} or {@code null} otherwise. 144 */ 145 @Nullable findBtDeviceStateForAddress(String address, boolean isBle)146 AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { 147 synchronized (mDeviceInventoryLock) { 148 final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET; 149 for (Integer internalType : deviceSet) { 150 AdiDeviceState deviceState = mDeviceInventory.get( 151 new Pair<>(internalType, address)); 152 if (deviceState != null) { 153 return deviceState; 154 } 155 } 156 } 157 return null; 158 } 159 160 /** 161 * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device 162 * type. Note: currently this method only returns a valid device for A2DP and BLE devices. 163 * 164 * @param ada attributes of device to match 165 * @param canonicalDeviceType external device type to match 166 * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or 167 * {@code null} otherwise. 168 */ 169 @Nullable findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalDeviceType)170 AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, 171 int canonicalDeviceType) { 172 final boolean isWireless = isBluetoothDevice(ada.getInternalType()); 173 synchronized (mDeviceInventoryLock) { 174 for (AdiDeviceState deviceState : mDeviceInventory.values()) { 175 if (deviceState.getDeviceType() == canonicalDeviceType 176 && (!isWireless || ada.getAddress().equals( 177 deviceState.getDeviceAddress()))) { 178 return deviceState; 179 } 180 } 181 } 182 return null; 183 } 184 185 /** Clears all cached {@link AdiDeviceState}'s. */ clearDeviceInventory()186 void clearDeviceInventory() { 187 synchronized (mDeviceInventoryLock) { 188 mDeviceInventory.clear(); 189 } 190 } 191 192 // List of connected devices 193 // Key for map created from DeviceInfo.makeDeviceListKey() 194 @GuardedBy("mDevicesLock") 195 private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() { 196 @Override 197 public DeviceInfo put(String key, DeviceInfo value) { 198 final DeviceInfo result = super.put(key, value); 199 record("put", true /* connected */, key, value); 200 return result; 201 } 202 203 @Override 204 public DeviceInfo putIfAbsent(String key, DeviceInfo value) { 205 final DeviceInfo result = super.putIfAbsent(key, value); 206 if (result == null) { 207 record("putIfAbsent", true /* connected */, key, value); 208 } 209 return result; 210 } 211 212 @Override 213 public DeviceInfo remove(Object key) { 214 final DeviceInfo result = super.remove(key); 215 if (result != null) { 216 record("remove", false /* connected */, (String) key, result); 217 } 218 return result; 219 } 220 221 @Override 222 public boolean remove(Object key, Object value) { 223 final boolean result = super.remove(key, value); 224 if (result) { 225 record("remove", false /* connected */, (String) key, (DeviceInfo) value); 226 } 227 return result; 228 } 229 230 // Not overridden 231 // clear 232 // compute 233 // computeIfAbsent 234 // computeIfPresent 235 // merge 236 // putAll 237 // replace 238 // replaceAll 239 private void record(String event, boolean connected, String key, DeviceInfo value) { 240 // DeviceInfo - int mDeviceType; 241 // DeviceInfo - int mDeviceCodecFormat; 242 new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE 243 + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType)) 244 .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress) 245 .set(MediaMetrics.Property.EVENT, event) 246 .set(MediaMetrics.Property.NAME, value.mDeviceName) 247 .set(MediaMetrics.Property.STATE, connected 248 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 249 .record(); 250 } 251 }; 252 253 // List of devices actually connected to AudioPolicy (through AudioSystem), only one 254 // by device type, which is used as the key, value is the DeviceInfo generated key. 255 // For the moment only for A2DP sink devices. 256 // TODO: extend to all device types 257 @GuardedBy("mDevicesLock") 258 private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>(); 259 260 // List of preferred devices for strategies 261 private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices = 262 new ArrayMap<>(); 263 264 // List of non-default devices for strategies 265 private final ArrayMap<Integer, List<AudioDeviceAttributes>> mNonDefaultDevices = 266 new ArrayMap<>(); 267 268 // List of preferred devices of capture preset 269 private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset = 270 new ArrayMap<>(); 271 272 // the wrapper for AudioSystem static methods, allows us to spy AudioSystem 273 private final @NonNull AudioSystemAdapter mAudioSystem; 274 275 private @NonNull AudioDeviceBroker mDeviceBroker; 276 277 // Monitoring of audio routes. Protected by mAudioRoutes. 278 final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); 279 final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = 280 new RemoteCallbackList<IAudioRoutesObserver>(); 281 282 // Monitoring of preferred device for strategies 283 final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers = 284 new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>(); 285 286 // Monitoring of non-default device for strategies 287 final RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher> mNonDefDevDispatchers = 288 new RemoteCallbackList<IStrategyNonDefaultDevicesDispatcher>(); 289 290 // Monitoring of devices for role and capture preset 291 final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = 292 new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); 293 294 final List<AudioProductStrategy> mStrategies; 295 AudioDeviceInventory(@onNull AudioDeviceBroker broker)296 /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { 297 this(broker, AudioSystemAdapter.getDefaultAdapter()); 298 } 299 300 //----------------------------------------------------------- 301 /** for mocking only, allows to inject AudioSystem adapter */ AudioDeviceInventory(@onNull AudioSystemAdapter audioSystem)302 /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) { 303 this(null, audioSystem); 304 } 305 AudioDeviceInventory(@ullable AudioDeviceBroker broker, @Nullable AudioSystemAdapter audioSystem)306 private AudioDeviceInventory(@Nullable AudioDeviceBroker broker, 307 @Nullable AudioSystemAdapter audioSystem) { 308 mDeviceBroker = broker; 309 mAudioSystem = audioSystem; 310 mStrategies = AudioProductStrategy.getAudioProductStrategies(); 311 mBluetoothDualModeEnabled = SystemProperties.getBoolean( 312 "persist.bluetooth.enable_dual_mode_audio", false); 313 } setDeviceBroker(@onNull AudioDeviceBroker broker)314 /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { 315 mDeviceBroker = broker; 316 } 317 318 //------------------------------------------------------------ 319 /** 320 * Class to store info about connected devices. 321 * Use makeDeviceListKey() to make a unique key for this list. 322 */ 323 private static class DeviceInfo { 324 final int mDeviceType; 325 final @NonNull String mDeviceName; 326 final @NonNull String mDeviceAddress; 327 int mDeviceCodecFormat; 328 final UUID mSensorUuid; 329 330 /** Disabled operating modes for this device. Use a negative logic so that by default 331 * an empty list means all modes are allowed. 332 * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */ 333 @NonNull ArraySet<String> mDisabledModes = new ArraySet(0); 334 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat, @Nullable UUID sensorUuid)335 DeviceInfo(int deviceType, String deviceName, String deviceAddress, 336 int deviceCodecFormat, @Nullable UUID sensorUuid) { 337 mDeviceType = deviceType; 338 mDeviceName = deviceName == null ? "" : deviceName; 339 mDeviceAddress = deviceAddress == null ? "" : deviceAddress; 340 mDeviceCodecFormat = deviceCodecFormat; 341 mSensorUuid = sensorUuid; 342 } 343 setModeDisabled(String mode)344 void setModeDisabled(String mode) { 345 mDisabledModes.add(mode); 346 } setModeEnabled(String mode)347 void setModeEnabled(String mode) { 348 mDisabledModes.remove(mode); 349 } isModeEnabled(String mode)350 boolean isModeEnabled(String mode) { 351 return !mDisabledModes.contains(mode); 352 } isOutputOnlyModeEnabled()353 boolean isOutputOnlyModeEnabled() { 354 return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); 355 } isDuplexModeEnabled()356 boolean isDuplexModeEnabled() { 357 return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); 358 } 359 DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat)360 DeviceInfo(int deviceType, String deviceName, String deviceAddress, 361 int deviceCodecFormat) { 362 this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); 363 } 364 DeviceInfo(int deviceType, String deviceName, String deviceAddress)365 DeviceInfo(int deviceType, String deviceName, String deviceAddress) { 366 this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT); 367 } 368 369 @Override toString()370 public String toString() { 371 return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) 372 + " (" + AudioSystem.getDeviceName(mDeviceType) 373 + ") name:" + mDeviceName 374 + " addr:" + mDeviceAddress 375 + " codec: " + Integer.toHexString(mDeviceCodecFormat) 376 + " sensorUuid: " + Objects.toString(mSensorUuid) 377 + " disabled modes: " + mDisabledModes + "]"; 378 } 379 getKey()380 @NonNull String getKey() { 381 return makeDeviceListKey(mDeviceType, mDeviceAddress); 382 } 383 384 /** 385 * Generate a unique key for the mConnectedDevices List by composing the device "type" 386 * and the "address" associated with a specific instance of that device type 387 */ makeDeviceListKey(int device, String deviceAddress)388 @NonNull private static String makeDeviceListKey(int device, String deviceAddress) { 389 return "0x" + Integer.toHexString(device) + ":" + deviceAddress; 390 } 391 } 392 393 /** 394 * A class just for packaging up a set of connection parameters. 395 */ 396 /*package*/ class WiredDeviceConnectionState { 397 public final AudioDeviceAttributes mAttributes; 398 public final @AudioService.ConnectionState int mState; 399 public final String mCaller; 400 public boolean mForTest = false; 401 WiredDeviceConnectionState(AudioDeviceAttributes attributes, @AudioService.ConnectionState int state, String caller)402 /*package*/ WiredDeviceConnectionState(AudioDeviceAttributes attributes, 403 @AudioService.ConnectionState int state, String caller) { 404 mAttributes = attributes; 405 mState = state; 406 mCaller = caller; 407 } 408 } 409 410 //------------------------------------------------------------ dump(PrintWriter pw, String prefix)411 /*package*/ void dump(PrintWriter pw, String prefix) { 412 pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET="); 413 BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> { 414 pw.print(" 0x" + Integer.toHexString(device)); }); 415 pw.println("\n" + prefix + "Preferred devices for strategy:"); 416 mPreferredDevices.forEach((strategy, device) -> { 417 pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); 418 pw.println("\n" + prefix + "Non-default devices for strategy:"); 419 mNonDefaultDevices.forEach((strategy, device) -> { 420 pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); 421 pw.println("\n" + prefix + "Connected devices:"); 422 mConnectedDevices.forEach((key, deviceInfo) -> { 423 pw.println(" " + prefix + deviceInfo.toString()); }); 424 pw.println("\n" + prefix + "APM Connected device (A2DP sink only):"); 425 mApmConnectedDevices.forEach((keyType, valueAddress) -> { 426 pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) 427 + " (" + AudioSystem.getDeviceName(keyType) 428 + ") addr:" + valueAddress); }); 429 pw.println("\n" + prefix + "Preferred devices for capture preset:"); 430 mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { 431 pw.println(" " + prefix + "capturePreset:" + capturePreset 432 + " devices:" + devices); }); 433 pw.println("\n" + prefix + "Applied devices roles for strategies (from API):"); 434 mAppliedStrategyRoles.forEach((key, devices) -> { 435 pw.println(" " + prefix + "strategy: " + key.first 436 + " role:" + key.second + " devices:" + devices); }); 437 pw.println("\n" + prefix + "Applied devices roles for strategies (internal):"); 438 mAppliedStrategyRolesInt.forEach((key, devices) -> { 439 pw.println(" " + prefix + "strategy: " + key.first 440 + " role:" + key.second + " devices:" + devices); }); 441 pw.println("\n" + prefix + "Applied devices roles for presets (from API):"); 442 mAppliedPresetRoles.forEach((key, devices) -> { 443 pw.println(" " + prefix + "preset: " + key.first 444 + " role:" + key.second + " devices:" + devices); }); 445 pw.println("\n" + prefix + "Applied devices roles for presets (internal:"); 446 mAppliedPresetRolesInt.forEach((key, devices) -> { 447 pw.println(" " + prefix + "preset: " + key.first 448 + " role:" + key.second + " devices:" + devices); }); 449 pw.println("\ndevices:\n"); 450 synchronized (mDeviceInventoryLock) { 451 for (AdiDeviceState device : mDeviceInventory.values()) { 452 pw.println("\t" + device + "\n"); 453 } 454 } 455 } 456 457 //------------------------------------------------------------ 458 // Message handling from AudioDeviceBroker 459 460 /** 461 * Restore previously connected devices. Use in case of audio server crash 462 * (see AudioService.onAudioServerDied() method) 463 */ 464 // Always executed on AudioDeviceBroker message queue onRestoreDevices()465 /*package*/ void onRestoreDevices() { 466 synchronized (mDevicesLock) { 467 //TODO iterate on mApmConnectedDevices instead once it handles all device types 468 for (DeviceInfo di : mConnectedDevices.values()) { 469 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(di.mDeviceType, 470 di.mDeviceAddress, 471 di.mDeviceName), 472 AudioSystem.DEVICE_STATE_AVAILABLE, 473 di.mDeviceCodecFormat); 474 } 475 mAppliedStrategyRolesInt.clear(); 476 mAppliedPresetRolesInt.clear(); 477 applyConnectedDevicesRoles_l(); 478 } 479 reapplyExternalDevicesRoles(); 480 } 481 reapplyExternalDevicesRoles()482 /*package*/ void reapplyExternalDevicesRoles() { 483 synchronized (mDevicesLock) { 484 mAppliedStrategyRoles.clear(); 485 mAppliedPresetRoles.clear(); 486 } 487 synchronized (mPreferredDevices) { 488 mPreferredDevices.forEach((strategy, devices) -> { 489 setPreferredDevicesForStrategy(strategy, devices); 490 }); 491 } 492 synchronized (mNonDefaultDevices) { 493 mNonDefaultDevices.forEach((strategy, devices) -> { 494 addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED, 495 devices, false /* internal */); 496 }); 497 } 498 synchronized (mPreferredDevicesForCapturePreset) { 499 mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { 500 setDevicesRoleForCapturePreset( 501 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 502 }); 503 } 504 } 505 506 // @GuardedBy("mDeviceBroker.mSetModeLock") 507 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") onSetBtActiveDevice(@onNull AudioDeviceBroker.BtDeviceInfo btInfo, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int streamType)508 void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, 509 @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, 510 int streamType) { 511 if (AudioService.DEBUG_DEVICES) { 512 Log.d(TAG, "onSetBtActiveDevice" 513 + " btDevice=" + btInfo.mDevice 514 + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile) 515 + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState)); 516 } 517 String address = btInfo.mDevice.getAddress(); 518 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 519 address = ""; 520 } 521 522 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:" 523 + btInfo + " codec=" + AudioSystem.audioFormatToString(codec))); 524 525 new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice") 526 .set(MediaMetrics.Property.STATUS, btInfo.mProfile) 527 .set(MediaMetrics.Property.DEVICE, 528 AudioSystem.getDeviceName(btInfo.mAudioSystemDevice)) 529 .set(MediaMetrics.Property.ADDRESS, address) 530 .set(MediaMetrics.Property.ENCODING, 531 AudioSystem.audioFormatToString(codec)) 532 .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice") 533 .set(MediaMetrics.Property.STREAM_TYPE, 534 AudioSystem.streamToString(streamType)) 535 .set(MediaMetrics.Property.STATE, 536 btInfo.mState == BluetoothProfile.STATE_CONNECTED 537 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) 538 .record(); 539 540 synchronized (mDevicesLock) { 541 final String key = DeviceInfo.makeDeviceListKey(btInfo.mAudioSystemDevice, address); 542 final DeviceInfo di = mConnectedDevices.get(key); 543 544 final boolean isConnected = di != null; 545 546 final boolean switchToUnavailable = isConnected 547 && btInfo.mState != BluetoothProfile.STATE_CONNECTED; 548 final boolean switchToAvailable = !isConnected 549 && btInfo.mState == BluetoothProfile.STATE_CONNECTED; 550 551 switch (btInfo.mProfile) { 552 case BluetoothProfile.A2DP_SINK: 553 if (switchToUnavailable) { 554 makeA2dpSrcUnavailable(address); 555 } else if (switchToAvailable) { 556 makeA2dpSrcAvailable(address); 557 } 558 break; 559 case BluetoothProfile.A2DP: 560 if (switchToUnavailable) { 561 makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); 562 } else if (switchToAvailable) { 563 // device is not already connected 564 if (btInfo.mVolume != -1) { 565 mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, 566 // convert index to internal representation in VolumeStreamState 567 btInfo.mVolume * 10, btInfo.mAudioSystemDevice, 568 "onSetBtActiveDevice"); 569 } 570 makeA2dpDeviceAvailable(btInfo, codec, "onSetBtActiveDevice"); 571 } 572 break; 573 case BluetoothProfile.HEARING_AID: 574 if (switchToUnavailable) { 575 makeHearingAidDeviceUnavailable(address); 576 } else if (switchToAvailable) { 577 makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), 578 streamType, "onSetBtActiveDevice"); 579 } 580 break; 581 case BluetoothProfile.LE_AUDIO: 582 case BluetoothProfile.LE_AUDIO_BROADCAST: 583 if (switchToUnavailable) { 584 makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); 585 } else if (switchToAvailable) { 586 makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice"); 587 } 588 break; 589 default: throw new IllegalArgumentException("Invalid profile " 590 + BluetoothProfile.getProfileName(btInfo.mProfile)); 591 } 592 } 593 } 594 595 596 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") onBluetoothDeviceConfigChange( @onNull AudioDeviceBroker.BtDeviceInfo btInfo, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event)597 /*package*/ void onBluetoothDeviceConfigChange( 598 @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, 599 @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) { 600 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 601 + "onBluetoothDeviceConfigChange") 602 .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event)); 603 604 final BluetoothDevice btDevice = btInfo.mDevice; 605 if (btDevice == null) { 606 mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); 607 return; 608 } 609 if (AudioService.DEBUG_DEVICES) { 610 Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice); 611 } 612 int volume = btInfo.mVolume; 613 614 String address = btDevice.getAddress(); 615 if (!BluetoothAdapter.checkBluetoothAddress(address)) { 616 address = ""; 617 } 618 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 619 "onBluetoothDeviceConfigChange addr=" + address 620 + " event=" + BtHelper.deviceEventToString(event))); 621 622 synchronized (mDevicesLock) { 623 if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) { 624 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 625 "A2dp config change ignored (scheduled connection change)") 626 .printLog(TAG)); 627 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored") 628 .record(); 629 return; 630 } 631 final String key = DeviceInfo.makeDeviceListKey( 632 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 633 final DeviceInfo di = mConnectedDevices.get(key); 634 if (di == null) { 635 Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange"); 636 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); 637 return; 638 } 639 640 mmi.set(MediaMetrics.Property.ADDRESS, address) 641 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec)) 642 .set(MediaMetrics.Property.INDEX, volume) 643 .set(MediaMetrics.Property.NAME, di.mDeviceName); 644 645 646 if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { 647 boolean a2dpCodecChange = false; 648 if (btInfo.mProfile == BluetoothProfile.A2DP) { 649 if (di.mDeviceCodecFormat != codec) { 650 di.mDeviceCodecFormat = codec; 651 mConnectedDevices.replace(key, di); 652 a2dpCodecChange = true; 653 } 654 final int res = mAudioSystem.handleDeviceConfigChange( 655 btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec); 656 657 if (res != AudioSystem.AUDIO_STATUS_OK) { 658 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 659 "APM handleDeviceConfigChange failed for A2DP device addr=" 660 + address + " codec=" 661 + AudioSystem.audioFormatToString(codec)) 662 .printLog(TAG)); 663 664 // force A2DP device disconnection in case of error so that AudioService 665 // state is consistent with audio policy manager state 666 setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo, 667 BluetoothProfile.STATE_DISCONNECTED)); 668 } else { 669 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 670 "APM handleDeviceConfigChange success for A2DP device addr=" 671 + address 672 + " codec=" + AudioSystem.audioFormatToString(codec)) 673 .printLog(TAG)); 674 675 } 676 } 677 if (!a2dpCodecChange) { 678 updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/); 679 } 680 } 681 } 682 mmi.record(); 683 } 684 onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec)685 /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { 686 synchronized (mDevicesLock) { 687 makeA2dpDeviceUnavailableNow(address, a2dpCodec); 688 } 689 } 690 onMakeLeAudioDeviceUnavailableNow(String address, int device)691 /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) { 692 synchronized (mDevicesLock) { 693 makeLeAudioDeviceUnavailableNow(address, device); 694 } 695 } 696 onReportNewRoutes()697 /*package*/ void onReportNewRoutes() { 698 int n = mRoutesObservers.beginBroadcast(); 699 if (n > 0) { 700 new MediaMetrics.Item(mMetricsId + "onReportNewRoutes") 701 .set(MediaMetrics.Property.OBSERVERS, n) 702 .record(); 703 AudioRoutesInfo routes; 704 synchronized (mCurAudioRoutes) { 705 routes = new AudioRoutesInfo(mCurAudioRoutes); 706 } 707 while (n > 0) { 708 n--; 709 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n); 710 try { 711 obs.dispatchAudioRoutesChanged(routes); 712 } catch (RemoteException e) { } 713 } 714 } 715 mRoutesObservers.finishBroadcast(); 716 mDeviceBroker.postObserveDevicesForAllStreams(); 717 } 718 719 /* package */ static final Set<Integer> DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET; 720 static { 721 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET = new HashSet<>(); 722 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 723 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 724 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE); 725 DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 726 } 727 onSetWiredDeviceConnectionState( AudioDeviceInventory.WiredDeviceConnectionState wdcs)728 /*package*/ void onSetWiredDeviceConnectionState( 729 AudioDeviceInventory.WiredDeviceConnectionState wdcs) { 730 int type = wdcs.mAttributes.getInternalType(); 731 732 AudioService.sDeviceLogger.enqueue(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); 733 734 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 735 + "onSetWiredDeviceConnectionState") 736 .set(MediaMetrics.Property.ADDRESS, wdcs.mAttributes.getAddress()) 737 .set(MediaMetrics.Property.DEVICE, 738 AudioSystem.getDeviceName(type)) 739 .set(MediaMetrics.Property.STATE, 740 wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED 741 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED); 742 AudioDeviceInfo info = null; 743 if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED 744 && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains( 745 wdcs.mAttributes.getInternalType())) { 746 for (AudioDeviceInfo deviceInfo : AudioManager.getDevicesStatic( 747 AudioManager.GET_DEVICES_OUTPUTS)) { 748 if (deviceInfo.getInternalType() == wdcs.mAttributes.getInternalType()) { 749 info = deviceInfo; 750 break; 751 } 752 } 753 } 754 synchronized (mDevicesLock) { 755 if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) 756 && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { 757 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, 758 "onSetWiredDeviceConnectionState state DISCONNECTED"); 759 } 760 761 if (!handleDeviceConnection(wdcs.mAttributes, 762 wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) { 763 // change of connection state failed, bailout 764 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") 765 .record(); 766 return; 767 } 768 if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) { 769 if (DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) { 770 mDeviceBroker.setBluetoothA2dpOnInt(false, false /*fromA2dp*/, 771 "onSetWiredDeviceConnectionState state not DISCONNECTED"); 772 } 773 mDeviceBroker.checkMusicActive(type, wdcs.mCaller); 774 } 775 if (type == AudioSystem.DEVICE_OUT_HDMI) { 776 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller); 777 } 778 if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED 779 && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains( 780 wdcs.mAttributes.getInternalType())) { 781 if (info != null) { 782 mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved( 783 info); 784 } else { 785 Log.e(TAG, "Didn't find AudioDeviceInfo to notify preferred mixer " 786 + "attributes change for type=" + wdcs.mAttributes.getType()); 787 } 788 } 789 sendDeviceConnectionIntent(type, wdcs.mState, 790 wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName()); 791 updateAudioRoutes(type, wdcs.mState); 792 } 793 mmi.record(); 794 } 795 onToggleHdmi()796 /*package*/ void onToggleHdmi() { 797 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi") 798 .set(MediaMetrics.Property.DEVICE, 799 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI)); 800 synchronized (mDevicesLock) { 801 // Is HDMI connected? 802 final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); 803 final DeviceInfo di = mConnectedDevices.get(key); 804 if (di == null) { 805 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi"); 806 mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record(); 807 return; 808 } 809 // Toggle HDMI to retrigger broadcast with proper formats. 810 setWiredDeviceConnectionState( 811 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), 812 AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect 813 setWiredDeviceConnectionState( 814 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""), 815 AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect 816 } 817 mmi.record(); 818 } 819 onSaveSetPreferredDevices(int strategy, @NonNull List<AudioDeviceAttributes> devices)820 /*package*/ void onSaveSetPreferredDevices(int strategy, 821 @NonNull List<AudioDeviceAttributes> devices) { 822 mPreferredDevices.put(strategy, devices); 823 List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy); 824 if (nonDefaultDevices != null) { 825 nonDefaultDevices.removeAll(devices); 826 827 if (nonDefaultDevices.isEmpty()) { 828 mNonDefaultDevices.remove(strategy); 829 } else { 830 mNonDefaultDevices.put(strategy, nonDefaultDevices); 831 } 832 dispatchNonDefaultDevice(strategy, nonDefaultDevices); 833 } 834 835 dispatchPreferredDevice(strategy, devices); 836 } 837 onSaveRemovePreferredDevices(int strategy)838 /*package*/ void onSaveRemovePreferredDevices(int strategy) { 839 mPreferredDevices.remove(strategy); 840 dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>()); 841 } 842 onSaveSetDeviceAsNonDefault(int strategy, @NonNull AudioDeviceAttributes device)843 /*package*/ void onSaveSetDeviceAsNonDefault(int strategy, 844 @NonNull AudioDeviceAttributes device) { 845 List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy); 846 if (nonDefaultDevices == null) { 847 nonDefaultDevices = new ArrayList<>(); 848 } 849 850 if (!nonDefaultDevices.contains(device)) { 851 nonDefaultDevices.add(device); 852 } 853 854 mNonDefaultDevices.put(strategy, nonDefaultDevices); 855 dispatchNonDefaultDevice(strategy, nonDefaultDevices); 856 857 List<AudioDeviceAttributes> preferredDevices = mPreferredDevices.get(strategy); 858 859 if (preferredDevices != null) { 860 preferredDevices.remove(device); 861 mPreferredDevices.put(strategy, preferredDevices); 862 863 dispatchPreferredDevice(strategy, preferredDevices); 864 } 865 } 866 onSaveRemoveDeviceAsNonDefault(int strategy, @NonNull AudioDeviceAttributes device)867 /*package*/ void onSaveRemoveDeviceAsNonDefault(int strategy, 868 @NonNull AudioDeviceAttributes device) { 869 List<AudioDeviceAttributes> nonDefaultDevices = mNonDefaultDevices.get(strategy); 870 if (nonDefaultDevices != null) { 871 nonDefaultDevices.remove(device); 872 mNonDefaultDevices.put(strategy, nonDefaultDevices); 873 dispatchNonDefaultDevice(strategy, nonDefaultDevices); 874 } 875 } 876 onSaveSetPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)877 /*package*/ void onSaveSetPreferredDevicesForCapturePreset( 878 int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { 879 mPreferredDevicesForCapturePreset.put(capturePreset, devices); 880 dispatchDevicesRoleForCapturePreset( 881 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 882 } 883 onSaveClearPreferredDevicesForCapturePreset(int capturePreset)884 /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) { 885 mPreferredDevicesForCapturePreset.remove(capturePreset); 886 dispatchDevicesRoleForCapturePreset( 887 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, 888 new ArrayList<AudioDeviceAttributes>()); 889 } 890 891 //------------------------------------------------------------ 892 // preferred/non-default device(s) 893 setPreferredDevicesForStrategyAndSave(int strategy, @NonNull List<AudioDeviceAttributes> devices)894 /*package*/ int setPreferredDevicesForStrategyAndSave(int strategy, 895 @NonNull List<AudioDeviceAttributes> devices) { 896 final int status = setPreferredDevicesForStrategy(strategy, devices); 897 if (status == AudioSystem.SUCCESS) { 898 mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices); 899 } 900 return status; 901 } 902 // Only used for external requests coming from an API setPreferredDevicesForStrategy(int strategy, @NonNull List<AudioDeviceAttributes> devices)903 /*package*/ int setPreferredDevicesForStrategy(int strategy, 904 @NonNull List<AudioDeviceAttributes> devices) { 905 int status = AudioSystem.ERROR; 906 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 907 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 908 "setPreferredDevicesForStrategy, strategy: " + strategy 909 + " devices: " + devices)).printLog(TAG)); 910 status = setDevicesRoleForStrategy( 911 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */); 912 } 913 return status; 914 } 915 // Only used for internal requests setPreferredDevicesForStrategyInt(int strategy, @NonNull List<AudioDeviceAttributes> devices)916 /*package*/ int setPreferredDevicesForStrategyInt(int strategy, 917 @NonNull List<AudioDeviceAttributes> devices) { 918 919 return setDevicesRoleForStrategy( 920 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, true /* internal */); 921 } 922 removePreferredDevicesForStrategyAndSave(int strategy)923 /*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) { 924 final int status = removePreferredDevicesForStrategy(strategy); 925 if (status == AudioSystem.SUCCESS) { 926 mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy); 927 } 928 return status; 929 } 930 // Only used for external requests coming from an API removePreferredDevicesForStrategy(int strategy)931 /*package*/ int removePreferredDevicesForStrategy(int strategy) { 932 int status = AudioSystem.ERROR; 933 934 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 935 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 936 "removePreferredDevicesForStrategy, strategy: " 937 + strategy)).printLog(TAG)); 938 939 status = clearDevicesRoleForStrategy( 940 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */); 941 } 942 return status; 943 } 944 // Only used for internal requests removePreferredDevicesForStrategyInt(int strategy)945 /*package*/ int removePreferredDevicesForStrategyInt(int strategy) { 946 return clearDevicesRoleForStrategy( 947 strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */); 948 } 949 setDeviceAsNonDefaultForStrategyAndSave(int strategy, @NonNull AudioDeviceAttributes device)950 /*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy, 951 @NonNull AudioDeviceAttributes device) { 952 int status = AudioSystem.ERROR; 953 954 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 955 List<AudioDeviceAttributes> devices = new ArrayList<>(); 956 devices.add(device); 957 958 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 959 "setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy 960 + " device: " + device)).printLog(TAG)); 961 status = addDevicesRoleForStrategy( 962 strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */); 963 } 964 965 if (status == AudioSystem.SUCCESS) { 966 mDeviceBroker.postSaveSetDeviceAsNonDefaultForStrategy(strategy, device); 967 } 968 return status; 969 } 970 removeDeviceAsNonDefaultForStrategyAndSave(int strategy, @NonNull AudioDeviceAttributes device)971 /*package*/ int removeDeviceAsNonDefaultForStrategyAndSave(int strategy, 972 @NonNull AudioDeviceAttributes device) { 973 int status = AudioSystem.ERROR; 974 975 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 976 List<AudioDeviceAttributes> devices = new ArrayList<>(); 977 devices.add(device); 978 979 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 980 "removeDeviceAsNonDefaultForStrategyAndSave, strategy: " 981 + strategy + " devices: " + device)).printLog(TAG)); 982 983 status = removeDevicesRoleForStrategy( 984 strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */); 985 } 986 987 if (status == AudioSystem.SUCCESS) { 988 mDeviceBroker.postSaveRemoveDeviceAsNonDefaultForStrategy(strategy, device); 989 } 990 return status; 991 } 992 993 registerStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)994 /*package*/ void registerStrategyPreferredDevicesDispatcher( 995 @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { 996 mPrefDevDispatchers.register(dispatcher); 997 } 998 unregisterStrategyPreferredDevicesDispatcher( @onNull IStrategyPreferredDevicesDispatcher dispatcher)999 /*package*/ void unregisterStrategyPreferredDevicesDispatcher( 1000 @NonNull IStrategyPreferredDevicesDispatcher dispatcher) { 1001 mPrefDevDispatchers.unregister(dispatcher); 1002 } 1003 registerStrategyNonDefaultDevicesDispatcher( @onNull IStrategyNonDefaultDevicesDispatcher dispatcher)1004 /*package*/ void registerStrategyNonDefaultDevicesDispatcher( 1005 @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { 1006 mNonDefDevDispatchers.register(dispatcher); 1007 } 1008 unregisterStrategyNonDefaultDevicesDispatcher( @onNull IStrategyNonDefaultDevicesDispatcher dispatcher)1009 /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher( 1010 @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) { 1011 mNonDefDevDispatchers.unregister(dispatcher); 1012 } 1013 setPreferredDevicesForCapturePresetAndSave( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)1014 /*package*/ int setPreferredDevicesForCapturePresetAndSave( 1015 int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { 1016 final int status = setPreferredDevicesForCapturePreset(capturePreset, devices); 1017 if (status == AudioSystem.SUCCESS) { 1018 mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices); 1019 } 1020 return status; 1021 } 1022 1023 // Only used for external requests coming from an API setPreferredDevicesForCapturePreset( int capturePreset, @NonNull List<AudioDeviceAttributes> devices)1024 private int setPreferredDevicesForCapturePreset( 1025 int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { 1026 int status = AudioSystem.ERROR; 1027 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 1028 status = setDevicesRoleForCapturePreset( 1029 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); 1030 } 1031 return status; 1032 } 1033 clearPreferredDevicesForCapturePresetAndSave(int capturePreset)1034 /*package*/ int clearPreferredDevicesForCapturePresetAndSave(int capturePreset) { 1035 final int status = clearPreferredDevicesForCapturePreset(capturePreset); 1036 if (status == AudioSystem.SUCCESS) { 1037 mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset); 1038 } 1039 return status; 1040 } 1041 1042 // Only used for external requests coming from an API clearPreferredDevicesForCapturePreset(int capturePreset)1043 private int clearPreferredDevicesForCapturePreset(int capturePreset) { 1044 int status = AudioSystem.ERROR; 1045 1046 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 1047 status = clearDevicesRoleForCapturePreset( 1048 capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED); 1049 } 1050 return status; 1051 } 1052 1053 // Only used for internal requests addDevicesRoleForCapturePresetInt(int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1054 private int addDevicesRoleForCapturePresetInt(int capturePreset, int role, 1055 @NonNull List<AudioDeviceAttributes> devices) { 1056 return addDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> { 1057 return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d); 1058 }, capturePreset, role, devices); 1059 } 1060 1061 // Only used for internal requests removeDevicesRoleForCapturePresetInt(int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices)1062 private int removeDevicesRoleForCapturePresetInt(int capturePreset, int role, 1063 @NonNull List<AudioDeviceAttributes> devices) { 1064 return removeDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> { 1065 return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); 1066 }, capturePreset, role, devices); 1067 } 1068 1069 // Only used for external requests coming from an API 1070 private int setDevicesRoleForCapturePreset(int capturePreset, int role, 1071 @NonNull List<AudioDeviceAttributes> devices) { 1072 return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> { 1073 return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d); 1074 }, (p, r, d) -> { 1075 return mAudioSystem.clearDevicesRoleForCapturePreset(p, r); 1076 }, capturePreset, role, devices); 1077 } 1078 1079 // Only used for external requests coming from an API 1080 private int clearDevicesRoleForCapturePreset(int capturePreset, int role) { 1081 return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> { 1082 return mAudioSystem.clearDevicesRoleForCapturePreset(p, r); 1083 }, capturePreset, role); 1084 } 1085 1086 /*package*/ void registerCapturePresetDevicesRoleDispatcher( 1087 @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { 1088 mDevRoleCapturePresetDispatchers.register(dispatcher); 1089 } 1090 1091 /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( 1092 @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { 1093 mDevRoleCapturePresetDispatchers.unregister(dispatcher); 1094 } 1095 1096 private int addDevicesRoleForStrategy(int strategy, int role, 1097 @NonNull List<AudioDeviceAttributes> devices, 1098 boolean internal) { 1099 return addDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles, 1100 (s, r, d) -> { 1101 return mAudioSystem.setDevicesRoleForStrategy(s, r, d); 1102 }, strategy, role, devices); 1103 } 1104 1105 private int removeDevicesRoleForStrategy(int strategy, int role, 1106 @NonNull List<AudioDeviceAttributes> devices, 1107 boolean internal) { 1108 return removeDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles, 1109 (s, r, d) -> { 1110 return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); 1111 }, strategy, role, devices); 1112 } 1113 1114 private int setDevicesRoleForStrategy(int strategy, int role, 1115 @NonNull List<AudioDeviceAttributes> devices, 1116 boolean internal) { 1117 return setDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles, 1118 (s, r, d) -> { 1119 return mAudioSystem.setDevicesRoleForStrategy(s, r, d); 1120 }, (s, r, d) -> { 1121 return mAudioSystem.clearDevicesRoleForStrategy(s, r); 1122 }, strategy, role, devices); 1123 } 1124 1125 private int clearDevicesRoleForStrategy(int strategy, int role, boolean internal) { 1126 return clearDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles, 1127 (s, r, d) -> { 1128 return mAudioSystem.clearDevicesRoleForStrategy(s, r); 1129 }, strategy, role); 1130 } 1131 1132 //------------------------------------------------------------ 1133 // Cache for applied roles for strategies and devices. The cache avoids reapplying the 1134 // same list of devices for a given role and strategy and the corresponding systematic 1135 // redundant work in audio policy manager and audio flinger. 1136 // The key is the pair <Strategy , Role> and the value is the current list of devices. 1137 // mAppliedStrategyRoles is for requests coming from an API. 1138 // mAppliedStrategyRolesInt is for internal requests. Entries are removed when the requested 1139 // device is disconnected. 1140 private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> 1141 mAppliedStrategyRoles = new ArrayMap<>(); 1142 private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> 1143 mAppliedStrategyRolesInt = new ArrayMap<>(); 1144 1145 // Cache for applied roles for capture presets and devices. The cache avoids reapplying the 1146 // same list of devices for a given role and capture preset and the corresponding systematic 1147 // redundant work in audio policy manager and audio flinger. 1148 // The key is the pair <Preset , Role> and the value is the current list of devices. 1149 // mAppliedPresetRoles is for requests coming from an API. 1150 // mAppliedPresetRolesInt is for internal requests. Entries are removed when the requested 1151 // device is disconnected. 1152 private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> 1153 mAppliedPresetRoles = new ArrayMap<>(); 1154 private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> 1155 mAppliedPresetRolesInt = new ArrayMap<>(); 1156 1157 interface AudioSystemInterface { 1158 int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices); 1159 } 1160 1161 private int addDevicesRole( 1162 ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, 1163 AudioSystemInterface asi, 1164 int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { 1165 synchronized (rolesMap) { 1166 Pair<Integer, Integer> key = new Pair<>(useCase, role); 1167 List<AudioDeviceAttributes> roleDevices = new ArrayList<>(); 1168 List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); 1169 1170 if (rolesMap.containsKey(key)) { 1171 roleDevices = rolesMap.get(key); 1172 for (AudioDeviceAttributes device : devices) { 1173 if (!roleDevices.contains(device)) { 1174 appliedDevices.add(device); 1175 } 1176 } 1177 } else { 1178 appliedDevices.addAll(devices); 1179 } 1180 if (appliedDevices.isEmpty()) { 1181 return AudioSystem.SUCCESS; 1182 } 1183 final int status = asi.deviceRoleAction(useCase, role, appliedDevices); 1184 if (status == AudioSystem.SUCCESS) { 1185 roleDevices.addAll(appliedDevices); 1186 rolesMap.put(key, roleDevices); 1187 } 1188 return status; 1189 } 1190 } 1191 1192 private int removeDevicesRole( 1193 ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, 1194 AudioSystemInterface asi, 1195 int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { 1196 synchronized (rolesMap) { 1197 Pair<Integer, Integer> key = new Pair<>(useCase, role); 1198 if (!rolesMap.containsKey(key)) { 1199 // trying to remove a role for a device that wasn't set 1200 return AudioSystem.BAD_VALUE; 1201 } 1202 List<AudioDeviceAttributes> roleDevices = rolesMap.get(key); 1203 List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); 1204 for (AudioDeviceAttributes device : devices) { 1205 if (roleDevices.contains(device)) { 1206 appliedDevices.add(device); 1207 } 1208 } 1209 if (appliedDevices.isEmpty()) { 1210 return AudioSystem.SUCCESS; 1211 } 1212 final int status = asi.deviceRoleAction(useCase, role, appliedDevices); 1213 if (status == AudioSystem.SUCCESS) { 1214 roleDevices.removeAll(appliedDevices); 1215 if (roleDevices.isEmpty()) { 1216 rolesMap.remove(key); 1217 } else { 1218 rolesMap.put(key, roleDevices); 1219 } 1220 } 1221 return status; 1222 } 1223 } 1224 1225 private int setDevicesRole( 1226 ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, 1227 AudioSystemInterface addOp, 1228 AudioSystemInterface clearOp, 1229 int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { 1230 synchronized (rolesMap) { 1231 Pair<Integer, Integer> key = new Pair<>(useCase, role); 1232 List<AudioDeviceAttributes> roleDevices = new ArrayList<>(); 1233 List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); 1234 1235 if (rolesMap.containsKey(key)) { 1236 roleDevices = rolesMap.get(key); 1237 boolean equal = false; 1238 if (roleDevices.size() == devices.size()) { 1239 roleDevices.retainAll(devices); 1240 equal = roleDevices.size() == devices.size(); 1241 } 1242 if (!equal) { 1243 clearOp.deviceRoleAction(useCase, role, null); 1244 roleDevices.clear(); 1245 appliedDevices.addAll(devices); 1246 } 1247 } else { 1248 appliedDevices.addAll(devices); 1249 } 1250 if (appliedDevices.isEmpty()) { 1251 return AudioSystem.SUCCESS; 1252 } 1253 final int status = addOp.deviceRoleAction(useCase, role, appliedDevices); 1254 if (status == AudioSystem.SUCCESS) { 1255 roleDevices.addAll(appliedDevices); 1256 rolesMap.put(key, roleDevices); 1257 } 1258 return status; 1259 } 1260 } 1261 1262 private int clearDevicesRole( 1263 ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, 1264 AudioSystemInterface asi, int useCase, int role) { 1265 synchronized (rolesMap) { 1266 Pair<Integer, Integer> key = new Pair<>(useCase, role); 1267 if (!rolesMap.containsKey(key)) { 1268 // trying to clear a role for a device that wasn't set 1269 return AudioSystem.BAD_VALUE; 1270 } 1271 final int status = asi.deviceRoleAction(useCase, role, null); 1272 if (status == AudioSystem.SUCCESS) { 1273 rolesMap.remove(key); 1274 } 1275 return status; 1276 } 1277 } 1278 1279 @GuardedBy("mDevicesLock") 1280 private void purgeDevicesRoles_l() { 1281 purgeRoles(mAppliedStrategyRolesInt, (s, r, d) -> { 1282 return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); }); 1283 purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> { 1284 return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); }); 1285 reapplyExternalDevicesRoles(); 1286 } 1287 1288 @GuardedBy("mDevicesLock") 1289 private void purgeRoles( 1290 ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, 1291 AudioSystemInterface asi) { 1292 synchronized (rolesMap) { 1293 AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic( 1294 AudioManager.GET_DEVICES_ALL); 1295 1296 Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = 1297 rolesMap.entrySet().iterator(); 1298 1299 while (itRole.hasNext()) { 1300 Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = 1301 itRole.next(); 1302 Pair<Integer, Integer> keyRole = entry.getKey(); 1303 Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator(); 1304 while (itDev.hasNext()) { 1305 AudioDeviceAttributes ada = itDev.next(); 1306 1307 AudioDeviceInfo device = Stream.of(connectedDevices) 1308 .filter(d -> d.getInternalType() == ada.getInternalType()) 1309 .filter(d -> (!isBluetoothDevice(d.getInternalType()) 1310 || (d.getAddress().equals(ada.getAddress())))) 1311 .findFirst() 1312 .orElse(null); 1313 1314 if (device == null) { 1315 if (AudioService.DEBUG_DEVICES) { 1316 Slog.i(TAG, "purgeRoles() removing device: " + ada.toString() 1317 + ", for strategy: " + keyRole.first 1318 + " and role: " + keyRole.second); 1319 } 1320 asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada)); 1321 itDev.remove(); 1322 } 1323 } 1324 if (rolesMap.get(keyRole).isEmpty()) { 1325 itRole.remove(); 1326 } 1327 } 1328 } 1329 } 1330 1331 //----------------------------------------------------------------------- 1332 1333 /** 1334 * Check if a device is in the list of connected devices 1335 * @param device the device whose connection state is queried 1336 * @return true if connected 1337 */ 1338 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") 1339 public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) { 1340 final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), 1341 device.getAddress()); 1342 synchronized (mDevicesLock) { 1343 return (mConnectedDevices.get(key) != null); 1344 } 1345 } 1346 1347 /** 1348 * Implements the communication with AudioSystem to (dis)connect a device in the native layers 1349 * @param attributes the attributes of the device 1350 * @param connect true if connection 1351 * @param isForTesting if true, not calling AudioSystem for the connection as this is 1352 * just for testing 1353 * @param btDevice the corresponding Bluetooth device when relevant. 1354 * @return false if an error was reported by AudioSystem 1355 */ 1356 /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, 1357 boolean connect, boolean isForTesting, 1358 @Nullable BluetoothDevice btDevice) { 1359 int device = attributes.getInternalType(); 1360 String address = attributes.getAddress(); 1361 String deviceName = attributes.getName(); 1362 if (AudioService.DEBUG_DEVICES) { 1363 Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" 1364 + Integer.toHexString(device) + " address:" + address 1365 + " name:" + deviceName + ")"); 1366 } 1367 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection") 1368 .set(MediaMetrics.Property.ADDRESS, address) 1369 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 1370 .set(MediaMetrics.Property.MODE, connect 1371 ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) 1372 .set(MediaMetrics.Property.NAME, deviceName); 1373 boolean status = false; 1374 synchronized (mDevicesLock) { 1375 final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); 1376 if (AudioService.DEBUG_DEVICES) { 1377 Slog.i(TAG, "deviceKey:" + deviceKey); 1378 } 1379 DeviceInfo di = mConnectedDevices.get(deviceKey); 1380 boolean isConnected = di != null; 1381 if (AudioService.DEBUG_DEVICES) { 1382 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); 1383 } 1384 if (connect && !isConnected) { 1385 final int res; 1386 if (isForTesting) { 1387 res = AudioSystem.AUDIO_STATUS_OK; 1388 } else { 1389 res = mAudioSystem.setDeviceConnectionState(attributes, 1390 AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); 1391 } 1392 if (res != AudioSystem.AUDIO_STATUS_OK) { 1393 final String reason = "not connecting device 0x" + Integer.toHexString(device) 1394 + " due to command error " + res; 1395 Slog.e(TAG, reason); 1396 mmi.set(MediaMetrics.Property.EARLY_RETURN, reason) 1397 .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED) 1398 .record(); 1399 return false; 1400 } 1401 mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address)); 1402 mDeviceBroker.postAccessoryPlugMediaUnmute(device); 1403 status = true; 1404 } else if (!connect && isConnected) { 1405 mAudioSystem.setDeviceConnectionState(attributes, 1406 AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); 1407 // always remove even if disconnection failed 1408 mConnectedDevices.remove(deviceKey); 1409 mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes); 1410 status = true; 1411 } 1412 if (status) { 1413 if (AudioSystem.isBluetoothScoDevice(device)) { 1414 updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/); 1415 if (!connect) { 1416 purgeDevicesRoles_l(); 1417 } 1418 } 1419 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); 1420 } else { 1421 Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey 1422 + ", deviceSpec=" + di + ", connect=" + connect); 1423 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); 1424 } 1425 } 1426 return status; 1427 } 1428 1429 1430 private void disconnectA2dp() { 1431 synchronized (mDevicesLock) { 1432 final ArraySet<String> toRemove = new ArraySet<>(); 1433 // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices 1434 mConnectedDevices.values().forEach(deviceInfo -> { 1435 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { 1436 toRemove.add(deviceInfo.mDeviceAddress); 1437 } 1438 }); 1439 new MediaMetrics.Item(mMetricsId + "disconnectA2dp") 1440 .set(MediaMetrics.Property.EVENT, "disconnectA2dp") 1441 .record(); 1442 if (toRemove.size() > 0) { 1443 final int delay = checkSendBecomingNoisyIntentInt( 1444 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 1445 AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); 1446 toRemove.stream().forEach(deviceAddress -> 1447 makeA2dpDeviceUnavailableLater(deviceAddress, delay) 1448 ); 1449 } 1450 } 1451 } 1452 1453 private void disconnectA2dpSink() { 1454 synchronized (mDevicesLock) { 1455 final ArraySet<String> toRemove = new ArraySet<>(); 1456 // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices 1457 mConnectedDevices.values().forEach(deviceInfo -> { 1458 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { 1459 toRemove.add(deviceInfo.mDeviceAddress); 1460 } 1461 }); 1462 new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink") 1463 .set(MediaMetrics.Property.EVENT, "disconnectA2dpSink") 1464 .record(); 1465 toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress)); 1466 } 1467 } 1468 1469 private void disconnectHearingAid() { 1470 synchronized (mDevicesLock) { 1471 final ArraySet<String> toRemove = new ArraySet<>(); 1472 // Disconnect ALL DEVICE_OUT_HEARING_AID devices 1473 mConnectedDevices.values().forEach(deviceInfo -> { 1474 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { 1475 toRemove.add(deviceInfo.mDeviceAddress); 1476 } 1477 }); 1478 new MediaMetrics.Item(mMetricsId + "disconnectHearingAid") 1479 .set(MediaMetrics.Property.EVENT, "disconnectHearingAid") 1480 .record(); 1481 if (toRemove.size() > 0) { 1482 final int delay = checkSendBecomingNoisyIntentInt( 1483 AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE); 1484 toRemove.stream().forEach(deviceAddress -> 1485 // TODO delay not used? 1486 makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/) 1487 ); 1488 } 1489 } 1490 } 1491 1492 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") 1493 /*package*/ void onBtProfileDisconnected(int profile) { 1494 switch (profile) { 1495 case BluetoothProfile.HEADSET: 1496 disconnectHeadset(); 1497 break; 1498 case BluetoothProfile.A2DP: 1499 disconnectA2dp(); 1500 break; 1501 case BluetoothProfile.A2DP_SINK: 1502 disconnectA2dpSink(); 1503 break; 1504 case BluetoothProfile.HEARING_AID: 1505 disconnectHearingAid(); 1506 break; 1507 case BluetoothProfile.LE_AUDIO: 1508 disconnectLeAudioUnicast(); 1509 break; 1510 case BluetoothProfile.LE_AUDIO_BROADCAST: 1511 disconnectLeAudioBroadcast(); 1512 break; 1513 default: 1514 // Not a valid profile to disconnect 1515 Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " 1516 + BluetoothProfile.getProfileName(profile)); 1517 break; 1518 } 1519 } 1520 1521 /*package*/ void disconnectLeAudio(int device) { 1522 if (device != AudioSystem.DEVICE_OUT_BLE_HEADSET 1523 && device != AudioSystem.DEVICE_OUT_BLE_BROADCAST) { 1524 Log.e(TAG, "disconnectLeAudio: Can't disconnect not LE Audio device " + device); 1525 return; 1526 } 1527 1528 synchronized (mDevicesLock) { 1529 final ArraySet<String> toRemove = new ArraySet<>(); 1530 // Disconnect ALL DEVICE_OUT_BLE_HEADSET or DEVICE_OUT_BLE_BROADCAST devices 1531 mConnectedDevices.values().forEach(deviceInfo -> { 1532 if (deviceInfo.mDeviceType == device) { 1533 toRemove.add(deviceInfo.mDeviceAddress); 1534 } 1535 }); 1536 new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") 1537 .set(MediaMetrics.Property.EVENT, "disconnectLeAudio") 1538 .record(); 1539 if (toRemove.size() > 0) { 1540 final int delay = checkSendBecomingNoisyIntentInt(device, 1541 AudioService.CONNECTION_STATE_DISCONNECTED, 1542 AudioSystem.DEVICE_NONE); 1543 toRemove.stream().forEach(deviceAddress -> 1544 makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay) 1545 ); 1546 } 1547 } 1548 } 1549 1550 /*package*/ void disconnectLeAudioUnicast() { 1551 disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_HEADSET); 1552 } 1553 1554 /*package*/ void disconnectLeAudioBroadcast() { 1555 disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST); 1556 } 1557 1558 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") 1559 private void disconnectHeadset() { 1560 boolean disconnect = false; 1561 synchronized (mDevicesLock) { 1562 for (DeviceInfo di : mConnectedDevices.values()) { 1563 if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { 1564 // There is only one HFP active device and setting the active 1565 // device to null will disconnect both in and out devices 1566 disconnect = true; 1567 break; 1568 } 1569 } 1570 } 1571 if (disconnect) { 1572 mDeviceBroker.onSetBtScoActiveDevice(null); 1573 } 1574 } 1575 1576 // must be called before removing the device from mConnectedDevices 1577 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 1578 // from AudioSystem 1579 /*package*/ int checkSendBecomingNoisyIntent(int device, 1580 @AudioService.ConnectionState int state, int musicDevice) { 1581 synchronized (mDevicesLock) { 1582 return checkSendBecomingNoisyIntentInt(device, state, musicDevice); 1583 } 1584 } 1585 1586 /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { 1587 synchronized (mCurAudioRoutes) { 1588 AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); 1589 mRoutesObservers.register(observer); 1590 return routes; 1591 } 1592 } 1593 1594 /*package*/ AudioRoutesInfo getCurAudioRoutes() { 1595 return mCurAudioRoutes; 1596 } 1597 1598 /** 1599 * Set a Bluetooth device to active. 1600 */ 1601 @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") 1602 public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) { 1603 int delay; 1604 synchronized (mDevicesLock) { 1605 if (!info.mSupprNoisy 1606 && (((info.mProfile == BluetoothProfile.LE_AUDIO 1607 || info.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) 1608 && info.mIsLeOutput) 1609 || info.mProfile == BluetoothProfile.HEARING_AID 1610 || info.mProfile == BluetoothProfile.A2DP)) { 1611 @AudioService.ConnectionState int asState = 1612 (info.mState == BluetoothProfile.STATE_CONNECTED) 1613 ? AudioService.CONNECTION_STATE_CONNECTED 1614 : AudioService.CONNECTION_STATE_DISCONNECTED; 1615 delay = checkSendBecomingNoisyIntentInt(info.mAudioSystemDevice, asState, 1616 info.mMusicDevice); 1617 } else { 1618 delay = 0; 1619 } 1620 1621 if (AudioService.DEBUG_DEVICES) { 1622 Log.i(TAG, "setBluetoothActiveDevice " + info.toString() + " delay(ms): " + delay); 1623 } 1624 mDeviceBroker.postBluetoothActiveDevice(info, delay); 1625 if (info.mProfile == BluetoothProfile.HEARING_AID 1626 && info.mState == BluetoothProfile.STATE_CONNECTED) { 1627 mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE, 1628 "HEARING_AID set to CONNECTED"); 1629 } 1630 } 1631 return delay; 1632 } 1633 1634 /*package*/ int setWiredDeviceConnectionState(AudioDeviceAttributes attributes, 1635 @AudioService.ConnectionState int state, String caller) { 1636 synchronized (mDevicesLock) { 1637 int delay = checkSendBecomingNoisyIntentInt( 1638 attributes.getInternalType(), state, AudioSystem.DEVICE_NONE); 1639 mDeviceBroker.postSetWiredDeviceConnectionState( 1640 new WiredDeviceConnectionState(attributes, state, caller), delay); 1641 return delay; 1642 } 1643 } 1644 1645 /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device, 1646 @AudioService.ConnectionState int state) { 1647 final WiredDeviceConnectionState connection = new WiredDeviceConnectionState( 1648 device, state, "com.android.server.audio"); 1649 connection.mForTest = true; 1650 onSetWiredDeviceConnectionState(connection); 1651 } 1652 1653 //------------------------------------------------------------------- 1654 // Internal utilities 1655 1656 @GuardedBy("mDevicesLock") 1657 private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo, 1658 @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, 1659 String eventSource) { 1660 final String address = btInfo.mDevice.getAddress(); 1661 final String name = BtHelper.getName(btInfo.mDevice); 1662 1663 // enable A2DP before notifying A2DP connection to avoid unnecessary processing in 1664 // audio policy manager 1665 mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); 1666 // at this point there could be another A2DP device already connected in APM, but it 1667 // doesn't matter as this new one will overwrite the previous one 1668 AudioDeviceAttributes ada = new AudioDeviceAttributes( 1669 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name); 1670 final int res = mAudioSystem.setDeviceConnectionState(ada, 1671 AudioSystem.DEVICE_STATE_AVAILABLE, codec); 1672 1673 // TODO: log in MediaMetrics once distinction between connection failure and 1674 // double connection is made. 1675 if (res != AudioSystem.AUDIO_STATUS_OK) { 1676 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 1677 "APM failed to make available A2DP device addr=" + address 1678 + " error=" + res).printLog(TAG)); 1679 // TODO: connection failed, stop here 1680 // TODO: return; 1681 } else { 1682 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 1683 "A2DP device addr=" + address + " now available").printLog(TAG)); 1684 } 1685 1686 // Reset A2DP suspend state each time a new sink is connected 1687 mDeviceBroker.clearA2dpSuspended(true /* internalOnly */); 1688 1689 // The convention for head tracking sensors associated with A2DP devices is to 1690 // use a UUID derived from the MAC address as follows: 1691 // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address 1692 UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); 1693 final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, 1694 address, codec, sensorUuid); 1695 final String diKey = di.getKey(); 1696 mConnectedDevices.put(diKey, di); 1697 // on a connection always overwrite the device seen by AudioPolicy, see comment above when 1698 // calling AudioSystem 1699 mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey); 1700 1701 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 1702 setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); 1703 1704 updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); 1705 } 1706 1707 static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, 1708 AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION, 1709 AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD}; 1710 1711 // reflects system property persist.bluetooth.enable_dual_mode_audio 1712 final boolean mBluetoothDualModeEnabled; 1713 /** 1714 * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED 1715 * or not according to their own and other devices modes. 1716 * The top priority is given to LE devices, then SCO ,then A2DP. 1717 */ 1718 @GuardedBy("mDevicesLock") 1719 private void applyConnectedDevicesRoles_l() { 1720 if (!mBluetoothDualModeEnabled) { 1721 return; 1722 } 1723 DeviceInfo leOutDevice = 1724 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET); 1725 DeviceInfo leInDevice = 1726 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET); 1727 DeviceInfo a2dpDevice = 1728 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); 1729 DeviceInfo scoOutDevice = 1730 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET); 1731 DeviceInfo scoInDevice = 1732 getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET); 1733 boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled()); 1734 boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled()) 1735 || (leInDevice != null && leInDevice.isDuplexModeEnabled()); 1736 AudioDeviceAttributes communicationDevice = 1737 mDeviceBroker.mActiveCommunicationDevice == null 1738 ? null : ((mDeviceBroker.isInCommunication() 1739 && mDeviceBroker.mActiveCommunicationDevice != null) 1740 ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice) 1741 : null); 1742 1743 if (AudioService.DEBUG_DEVICES) { 1744 Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice 1745 + "\n - leInDevice: " + leInDevice 1746 + "\n - a2dpDevice: " + a2dpDevice 1747 + "\n - scoOutDevice: " + scoOutDevice 1748 + "\n - scoInDevice: " + scoInDevice 1749 + "\n - disableA2dp: " + disableA2dp 1750 + ", disableSco: " + disableSco); 1751 } 1752 1753 for (DeviceInfo di : mConnectedDevices.values()) { 1754 if (!isBluetoothDevice(di.mDeviceType)) { 1755 continue; 1756 } 1757 AudioDeviceAttributes ada = 1758 new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName); 1759 if (AudioService.DEBUG_DEVICES) { 1760 Log.i(TAG, " + checking Device: " + ada); 1761 } 1762 if (ada.equalTypeAddress(communicationDevice)) { 1763 continue; 1764 } 1765 1766 if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) { 1767 for (AudioProductStrategy strategy : mStrategies) { 1768 boolean disable = false; 1769 if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) { 1770 if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { 1771 disable = disableSco || !di.isDuplexModeEnabled(); 1772 } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { 1773 disable = !di.isDuplexModeEnabled(); 1774 } 1775 } else { 1776 if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) { 1777 disable = disableA2dp || !di.isOutputOnlyModeEnabled(); 1778 } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { 1779 disable = disableSco || !di.isOutputOnlyModeEnabled(); 1780 } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { 1781 disable = !di.isOutputOnlyModeEnabled(); 1782 } 1783 } 1784 if (AudioService.DEBUG_DEVICES) { 1785 Log.i(TAG, " - strategy: " + strategy.getId() 1786 + ", disable: " + disable); 1787 } 1788 if (disable) { 1789 addDevicesRoleForStrategy(strategy.getId(), 1790 AudioSystem.DEVICE_ROLE_DISABLED, 1791 Arrays.asList(ada), true /* internal */); 1792 } else { 1793 removeDevicesRoleForStrategy(strategy.getId(), 1794 AudioSystem.DEVICE_ROLE_DISABLED, 1795 Arrays.asList(ada), true /* internal */); 1796 } 1797 } 1798 } 1799 if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) { 1800 for (int capturePreset : CAPTURE_PRESETS) { 1801 boolean disable = false; 1802 if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { 1803 disable = disableSco || !di.isDuplexModeEnabled(); 1804 } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { 1805 disable = !di.isDuplexModeEnabled(); 1806 } 1807 if (AudioService.DEBUG_DEVICES) { 1808 Log.i(TAG, " - capturePreset: " + capturePreset 1809 + ", disable: " + disable); 1810 } 1811 if (disable) { 1812 addDevicesRoleForCapturePresetInt(capturePreset, 1813 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); 1814 } else { 1815 removeDevicesRoleForCapturePresetInt(capturePreset, 1816 AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); 1817 } 1818 } 1819 } 1820 } 1821 } 1822 1823 /* package */ void applyConnectedDevicesRoles() { 1824 synchronized (mDevicesLock) { 1825 applyConnectedDevicesRoles_l(); 1826 } 1827 } 1828 1829 @GuardedBy("mDevicesLock") 1830 int checkProfileIsConnected(int profile) { 1831 switch (profile) { 1832 case BluetoothProfile.HEADSET: 1833 if (getFirstConnectedDeviceOfTypes( 1834 AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null 1835 || getFirstConnectedDeviceOfTypes( 1836 AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) { 1837 return profile; 1838 } 1839 break; 1840 case BluetoothProfile.A2DP: 1841 if (getFirstConnectedDeviceOfTypes( 1842 AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) { 1843 return profile; 1844 } 1845 break; 1846 case BluetoothProfile.LE_AUDIO: 1847 case BluetoothProfile.LE_AUDIO_BROADCAST: 1848 if (getFirstConnectedDeviceOfTypes( 1849 AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null 1850 || getFirstConnectedDeviceOfTypes( 1851 AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) { 1852 return profile; 1853 } 1854 break; 1855 default: 1856 break; 1857 } 1858 return 0; 1859 } 1860 1861 @GuardedBy("mDevicesLock") 1862 private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) { 1863 if (!mBluetoothDualModeEnabled) { 1864 return; 1865 } 1866 HashSet<String> processedAddresses = new HashSet<>(0); 1867 for (DeviceInfo di : mConnectedDevices.values()) { 1868 if (!isBluetoothDevice(di.mDeviceType) 1869 || processedAddresses.contains(di.mDeviceAddress)) { 1870 continue; 1871 } 1872 Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress); 1873 if (AudioService.DEBUG_DEVICES) { 1874 Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: " 1875 + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles); 1876 } 1877 for (DeviceInfo di2 : mConnectedDevices.values()) { 1878 if (!isBluetoothDevice(di2.mDeviceType) 1879 || !di.mDeviceAddress.equals(di2.mDeviceAddress)) { 1880 continue; 1881 } 1882 int profile = BtHelper.getProfileFromType(di2.mDeviceType); 1883 if (profile == 0) { 1884 continue; 1885 } 1886 int preferredProfile = checkProfileIsConnected( 1887 preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); 1888 if (preferredProfile == profile || preferredProfile == 0) { 1889 di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); 1890 } else { 1891 di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); 1892 } 1893 preferredProfile = checkProfileIsConnected( 1894 preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); 1895 if (preferredProfile == profile || preferredProfile == 0) { 1896 di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); 1897 } else { 1898 di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); 1899 } 1900 } 1901 processedAddresses.add(di.mDeviceAddress); 1902 } 1903 applyConnectedDevicesRoles_l(); 1904 if (connectedDevice != null) { 1905 mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice); 1906 } 1907 } 1908 1909 @GuardedBy("mDevicesLock") 1910 private void makeA2dpDeviceUnavailableNow(String address, int codec) { 1911 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address) 1912 .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec)) 1913 .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow"); 1914 1915 if (address == null) { 1916 mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record(); 1917 return; 1918 } 1919 final String deviceToRemoveKey = 1920 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 1921 1922 mConnectedDevices.remove(deviceToRemoveKey); 1923 if (!deviceToRemoveKey 1924 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { 1925 // removing A2DP device not currently used by AudioPolicy, log but don't act on it 1926 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 1927 "A2DP device " + address + " made unavailable, was not used")).printLog(TAG)); 1928 mmi.set(MediaMetrics.Property.EARLY_RETURN, 1929 "A2DP device made unavailable, was not used") 1930 .record(); 1931 return; 1932 } 1933 1934 // device to remove was visible by APM, update APM 1935 mDeviceBroker.clearAvrcpAbsoluteVolumeSupported(); 1936 AudioDeviceAttributes ada = new AudioDeviceAttributes( 1937 AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 1938 final int res = mAudioSystem.setDeviceConnectionState(ada, 1939 AudioSystem.DEVICE_STATE_UNAVAILABLE, codec); 1940 1941 if (res != AudioSystem.AUDIO_STATUS_OK) { 1942 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 1943 "APM failed to make unavailable A2DP device addr=" + address 1944 + " error=" + res).printLog(TAG)); 1945 // TODO: failed to disconnect, stop here 1946 // TODO: return; 1947 } else { 1948 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 1949 "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); 1950 } 1951 mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); 1952 1953 // Remove A2DP routes as well 1954 setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); 1955 mmi.record(); 1956 updateBluetoothPreferredModes_l(null /*connectedDevice*/); 1957 purgeDevicesRoles_l(); 1958 mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); 1959 } 1960 1961 @GuardedBy("mDevicesLock") 1962 private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { 1963 // prevent any activity on the A2DP audio output to avoid unwanted 1964 // reconnection of the sink. 1965 mDeviceBroker.setA2dpSuspended( 1966 true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater"); 1967 // retrieve DeviceInfo before removing device 1968 final String deviceKey = 1969 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); 1970 final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); 1971 final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat : 1972 AudioSystem.AUDIO_FORMAT_DEFAULT; 1973 // the device will be made unavailable later, so consider it disconnected right away 1974 mConnectedDevices.remove(deviceKey); 1975 // send the delayed message to make the device unavailable later 1976 mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs); 1977 } 1978 1979 1980 @GuardedBy("mDevicesLock") 1981 private void makeA2dpSrcAvailable(String address) { 1982 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 1983 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 1984 AudioSystem.DEVICE_STATE_AVAILABLE, 1985 AudioSystem.AUDIO_FORMAT_DEFAULT); 1986 mConnectedDevices.put( 1987 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), 1988 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address)); 1989 } 1990 1991 @GuardedBy("mDevicesLock") 1992 private void makeA2dpSrcUnavailable(String address) { 1993 AudioDeviceAttributes ada = new AudioDeviceAttributes( 1994 AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); 1995 mAudioSystem.setDeviceConnectionState(ada, 1996 AudioSystem.DEVICE_STATE_UNAVAILABLE, 1997 AudioSystem.AUDIO_FORMAT_DEFAULT); 1998 mConnectedDevices.remove( 1999 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); 2000 mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); 2001 } 2002 2003 @GuardedBy("mDevicesLock") 2004 private void makeHearingAidDeviceAvailable( 2005 String address, String name, int streamType, String eventSource) { 2006 final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, 2007 AudioSystem.DEVICE_OUT_HEARING_AID); 2008 mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType); 2009 2010 mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( 2011 AudioSystem.DEVICE_OUT_HEARING_AID, address, name), 2012 AudioSystem.DEVICE_STATE_AVAILABLE, 2013 AudioSystem.AUDIO_FORMAT_DEFAULT); 2014 mConnectedDevices.put( 2015 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), 2016 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address)); 2017 mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); 2018 mDeviceBroker.postApplyVolumeOnDevice(streamType, 2019 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); 2020 setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); 2021 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") 2022 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 2023 .set(MediaMetrics.Property.DEVICE, 2024 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 2025 .set(MediaMetrics.Property.NAME, name) 2026 .set(MediaMetrics.Property.STREAM_TYPE, 2027 AudioSystem.streamToString(streamType)) 2028 .record(); 2029 } 2030 2031 @GuardedBy("mDevicesLock") 2032 private void makeHearingAidDeviceUnavailable(String address) { 2033 AudioDeviceAttributes ada = new AudioDeviceAttributes( 2034 AudioSystem.DEVICE_OUT_HEARING_AID, address); 2035 mAudioSystem.setDeviceConnectionState(ada, 2036 AudioSystem.DEVICE_STATE_UNAVAILABLE, 2037 AudioSystem.AUDIO_FORMAT_DEFAULT); 2038 mConnectedDevices.remove( 2039 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address)); 2040 // Remove Hearing Aid routes as well 2041 setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); 2042 new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable") 2043 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") 2044 .set(MediaMetrics.Property.DEVICE, 2045 AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) 2046 .record(); 2047 mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); 2048 } 2049 2050 /** 2051 * Returns whether a device of type DEVICE_OUT_HEARING_AID is connected. 2052 * Visibility by APM plays no role 2053 * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise. 2054 */ 2055 boolean isHearingAidConnected() { 2056 return getFirstConnectedDeviceOfTypes( 2057 Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null; 2058 } 2059 2060 /** 2061 * Returns a DeviceInfo for the first connected device matching one of the supplied types 2062 */ 2063 private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) { 2064 List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes); 2065 return devices.isEmpty() ? null : devices.get(0); 2066 } 2067 2068 /** 2069 * Returns a list of connected devices matching one of the supplied types 2070 */ 2071 private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) { 2072 ArrayList<DeviceInfo> devices = new ArrayList<>(); 2073 synchronized (mDevicesLock) { 2074 for (DeviceInfo di : mConnectedDevices.values()) { 2075 if (internalTypes.contains(di.mDeviceType)) { 2076 devices.add(di); 2077 } 2078 } 2079 } 2080 return devices; 2081 } 2082 2083 /* package */ AudioDeviceAttributes getDeviceOfType(int type) { 2084 DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type)); 2085 return di == null ? null : new AudioDeviceAttributes( 2086 di.mDeviceType, di.mDeviceAddress, di.mDeviceName); 2087 } 2088 2089 @GuardedBy("mDevicesLock") 2090 private void makeLeAudioDeviceAvailable( 2091 AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) { 2092 final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10; 2093 final int device = btInfo.mAudioSystemDevice; 2094 2095 if (device != AudioSystem.DEVICE_NONE) { 2096 final String address = btInfo.mDevice.getAddress(); 2097 String name = BtHelper.getName(btInfo.mDevice); 2098 2099 // The BT Stack does not provide a name for LE Broadcast devices 2100 if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) { 2101 name = "Broadcast"; 2102 } 2103 2104 /* Audio Policy sees Le Audio similar to A2DP. Let's make sure 2105 * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set 2106 */ 2107 mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); 2108 2109 AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); 2110 final int res = AudioSystem.setDeviceConnectionState(ada, 2111 AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); 2112 if (res != AudioSystem.AUDIO_STATUS_OK) { 2113 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 2114 "APM failed to make available LE Audio device addr=" + address 2115 + " error=" + res).printLog(TAG)); 2116 // TODO: connection failed, stop here 2117 // TODO: return; 2118 } else { 2119 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 2120 "LE Audio device addr=" + address + " now available").printLog(TAG)); 2121 } 2122 // Reset LEA suspend state each time a new sink is connected 2123 mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */); 2124 2125 UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); 2126 mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), 2127 new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT, 2128 sensorUuid)); 2129 mDeviceBroker.postAccessoryPlugMediaUnmute(device); 2130 setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); 2131 } 2132 2133 if (streamType == AudioSystem.STREAM_DEFAULT) { 2134 // No need to update volume for input devices 2135 return; 2136 } 2137 2138 final int leAudioVolIndex = (volumeIndex == -1) 2139 ? mDeviceBroker.getVssVolumeForDevice(streamType, device) 2140 : volumeIndex; 2141 final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); 2142 mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); 2143 mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); 2144 2145 updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); 2146 } 2147 2148 @GuardedBy("mDevicesLock") 2149 private void makeLeAudioDeviceUnavailableNow(String address, int device) { 2150 AudioDeviceAttributes ada = null; 2151 if (device != AudioSystem.DEVICE_NONE) { 2152 ada = new AudioDeviceAttributes(device, address); 2153 final int res = AudioSystem.setDeviceConnectionState(ada, 2154 AudioSystem.DEVICE_STATE_UNAVAILABLE, 2155 AudioSystem.AUDIO_FORMAT_DEFAULT); 2156 2157 if (res != AudioSystem.AUDIO_STATUS_OK) { 2158 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 2159 "APM failed to make unavailable LE Audio device addr=" + address 2160 + " error=" + res).printLog(TAG)); 2161 // TODO: failed to disconnect, stop here 2162 // TODO: return; 2163 } else { 2164 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( 2165 "LE Audio device addr=" + address + " made unavailable").printLog(TAG)); 2166 } 2167 mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); 2168 } 2169 2170 setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); 2171 updateBluetoothPreferredModes_l(null /*connectedDevice*/); 2172 purgeDevicesRoles_l(); 2173 if (ada != null) { 2174 mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); 2175 } 2176 } 2177 2178 @GuardedBy("mDevicesLock") 2179 private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { 2180 // prevent any activity on the LEA output to avoid unwanted 2181 // reconnection of the sink. 2182 mDeviceBroker.setLeAudioSuspended( 2183 true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater"); 2184 // the device will be made unavailable later, so consider it disconnected right away 2185 mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); 2186 // send the delayed message to make the device unavailable later 2187 mDeviceBroker.setLeAudioTimeout(address, device, delayMs); 2188 } 2189 2190 @GuardedBy("mDevicesLock") 2191 private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { 2192 synchronized (mCurAudioRoutes) { 2193 if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { 2194 return; 2195 } 2196 if (name != null || !isCurrentDeviceConnected()) { 2197 mCurAudioRoutes.bluetoothName = name; 2198 mDeviceBroker.postReportNewRoutes(fromA2dp); 2199 } 2200 } 2201 } 2202 2203 @GuardedBy("mDevicesLock") 2204 private boolean isCurrentDeviceConnected() { 2205 return mConnectedDevices.values().stream().anyMatch(deviceInfo -> 2206 TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); 2207 } 2208 2209 // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only 2210 // sent if: 2211 // - none of these devices are connected anymore after one is disconnected AND 2212 // - the device being disconnected is actually used for music. 2213 // Access synchronized on mConnectedDevices 2214 private static final Set<Integer> BECOMING_NOISY_INTENT_DEVICES_SET; 2215 static { 2216 BECOMING_NOISY_INTENT_DEVICES_SET = new HashSet<>(); 2217 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET); 2218 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); 2219 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HDMI); 2220 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); 2221 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_LINE); 2222 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); 2223 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_HEADSET); 2224 BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_BLE_BROADCAST); 2225 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); 2226 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); 2227 BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET); 2228 } 2229 2230 // must be called before removing the device from mConnectedDevices 2231 // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying 2232 // from AudioSystem 2233 @GuardedBy("mDevicesLock") 2234 private int checkSendBecomingNoisyIntentInt(int device, 2235 @AudioService.ConnectionState int state, int musicDevice) { 2236 MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId 2237 + "checkSendBecomingNoisyIntentInt") 2238 .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device)) 2239 .set(MediaMetrics.Property.STATE, 2240 state == AudioService.CONNECTION_STATE_CONNECTED 2241 ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED); 2242 if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { 2243 Log.i(TAG, "not sending NOISY: state=" + state); 2244 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 2245 return 0; 2246 } 2247 if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) { 2248 Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device) 2249 + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET); 2250 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 2251 return 0; 2252 } 2253 int delay = 0; 2254 Set<Integer> devices = new HashSet<>(); 2255 for (DeviceInfo di : mConnectedDevices.values()) { 2256 if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0) 2257 && BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) { 2258 devices.add(di.mDeviceType); 2259 Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType)); 2260 } 2261 } 2262 if (musicDevice == AudioSystem.DEVICE_NONE) { 2263 musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); 2264 Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x" 2265 + Integer.toHexString(musicDevice)); 2266 } 2267 2268 // always ignore condition on device being actually used for music when in communication 2269 // because music routing is altered in this case. 2270 // also checks whether media routing if affected by a dynamic policy or mirroring 2271 final boolean inCommunication = mDeviceBroker.isInCommunication(); 2272 final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device); 2273 final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy(); 2274 if (((device == musicDevice) || inCommunication) 2275 && singleAudioDeviceType 2276 && !hasMediaDynamicPolicy 2277 && (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) { 2278 if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/) 2279 && !mDeviceBroker.hasAudioFocusUsers()) { 2280 // no media playback, not a "becoming noisy" situation, otherwise it could cause 2281 // the pausing of some apps that are playing remotely 2282 AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( 2283 "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG)); 2284 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return 2285 return 0; 2286 } 2287 mDeviceBroker.postBroadcastBecomingNoisy(); 2288 delay = AudioService.BECOMING_NOISY_DELAY_MS; 2289 } else { 2290 Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device) 2291 + " musicDevice:0x" + Integer.toHexString(musicDevice) 2292 + " inComm:" + inCommunication 2293 + " mediaPolicy:" + hasMediaDynamicPolicy 2294 + " singleDevice:" + singleAudioDeviceType); 2295 } 2296 2297 mmi.set(MediaMetrics.Property.DELAY_MS, delay).record(); 2298 return delay; 2299 } 2300 2301 // Intent "extra" data keys. 2302 private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; 2303 private static final String CONNECT_INTENT_KEY_STATE = "state"; 2304 private static final String CONNECT_INTENT_KEY_ADDRESS = "address"; 2305 private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; 2306 private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; 2307 private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; 2308 private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; 2309 2310 private void sendDeviceConnectionIntent(int device, int state, String address, 2311 String deviceName) { 2312 if (AudioService.DEBUG_DEVICES) { 2313 Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) 2314 + " state:0x" + Integer.toHexString(state) + " address:" + address 2315 + " name:" + deviceName + ");"); 2316 } 2317 Intent intent = new Intent(); 2318 2319 switch(device) { 2320 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 2321 intent.setAction(Intent.ACTION_HEADSET_PLUG); 2322 intent.putExtra("microphone", 1); 2323 break; 2324 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 2325 case AudioSystem.DEVICE_OUT_LINE: 2326 intent.setAction(Intent.ACTION_HEADSET_PLUG); 2327 intent.putExtra("microphone", 0); 2328 break; 2329 case AudioSystem.DEVICE_OUT_USB_HEADSET: 2330 intent.setAction(Intent.ACTION_HEADSET_PLUG); 2331 intent.putExtra("microphone", 2332 AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "") 2333 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0); 2334 break; 2335 case AudioSystem.DEVICE_IN_USB_HEADSET: 2336 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "") 2337 == AudioSystem.DEVICE_STATE_AVAILABLE) { 2338 intent.setAction(Intent.ACTION_HEADSET_PLUG); 2339 intent.putExtra("microphone", 1); 2340 } else { 2341 // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing 2342 return; 2343 } 2344 break; 2345 case AudioSystem.DEVICE_OUT_HDMI: 2346 case AudioSystem.DEVICE_OUT_HDMI_ARC: 2347 case AudioSystem.DEVICE_OUT_HDMI_EARC: 2348 configureHdmiPlugIntent(intent, state); 2349 break; 2350 } 2351 2352 if (intent.getAction() == null) { 2353 return; 2354 } 2355 2356 intent.putExtra(CONNECT_INTENT_KEY_STATE, state); 2357 intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); 2358 intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); 2359 2360 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); 2361 2362 final long ident = Binder.clearCallingIdentity(); 2363 try { 2364 mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent); 2365 } finally { 2366 Binder.restoreCallingIdentity(ident); 2367 } 2368 } 2369 2370 private void updateAudioRoutes(int device, int state) { 2371 int connType = 0; 2372 2373 switch (device) { 2374 case AudioSystem.DEVICE_OUT_WIRED_HEADSET: 2375 connType = AudioRoutesInfo.MAIN_HEADSET; 2376 break; 2377 case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE: 2378 case AudioSystem.DEVICE_OUT_LINE: 2379 connType = AudioRoutesInfo.MAIN_HEADPHONES; 2380 break; 2381 case AudioSystem.DEVICE_OUT_HDMI: 2382 case AudioSystem.DEVICE_OUT_HDMI_ARC: 2383 case AudioSystem.DEVICE_OUT_HDMI_EARC: 2384 connType = AudioRoutesInfo.MAIN_HDMI; 2385 break; 2386 case AudioSystem.DEVICE_OUT_USB_DEVICE: 2387 case AudioSystem.DEVICE_OUT_USB_HEADSET: 2388 connType = AudioRoutesInfo.MAIN_USB; 2389 break; 2390 case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET: 2391 connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; 2392 break; 2393 } 2394 2395 synchronized (mCurAudioRoutes) { 2396 if (connType == 0) { 2397 return; 2398 } 2399 int newConn = mCurAudioRoutes.mainType; 2400 if (state != 0) { 2401 newConn |= connType; 2402 } else { 2403 newConn &= ~connType; 2404 } 2405 if (newConn != mCurAudioRoutes.mainType) { 2406 mCurAudioRoutes.mainType = newConn; 2407 mDeviceBroker.postReportNewRoutes(false /*fromA2dp*/); 2408 } 2409 } 2410 } 2411 2412 private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) { 2413 intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); 2414 intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); 2415 if (state != AudioService.CONNECTION_STATE_CONNECTED) { 2416 return; 2417 } 2418 ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); 2419 int[] portGeneration = new int[1]; 2420 int status = AudioSystem.listAudioPorts(ports, portGeneration); 2421 if (status != AudioManager.SUCCESS) { 2422 Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent"); 2423 return; 2424 } 2425 for (AudioPort port : ports) { 2426 if (!(port instanceof AudioDevicePort)) { 2427 continue; 2428 } 2429 final AudioDevicePort devicePort = (AudioDevicePort) port; 2430 if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI 2431 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC 2432 && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_EARC) { 2433 continue; 2434 } 2435 // found an HDMI port: format the list of supported encodings 2436 int[] formats = AudioFormat.filterPublicFormats(devicePort.formats()); 2437 if (formats.length > 0) { 2438 ArrayList<Integer> encodingList = new ArrayList(1); 2439 for (int format : formats) { 2440 // a format in the list can be 0, skip it 2441 if (format != AudioFormat.ENCODING_INVALID) { 2442 encodingList.add(format); 2443 } 2444 } 2445 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray(); 2446 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); 2447 } 2448 // find the maximum supported number of channels 2449 int maxChannels = 0; 2450 for (int mask : devicePort.channelMasks()) { 2451 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); 2452 if (channelCount > maxChannels) { 2453 maxChannels = channelCount; 2454 } 2455 } 2456 intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); 2457 } 2458 } 2459 2460 private void dispatchPreferredDevice(int strategy, 2461 @NonNull List<AudioDeviceAttributes> devices) { 2462 final int nbDispatchers = mPrefDevDispatchers.beginBroadcast(); 2463 for (int i = 0; i < nbDispatchers; i++) { 2464 try { 2465 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged( 2466 strategy, devices); 2467 } catch (RemoteException e) { 2468 } 2469 } 2470 mPrefDevDispatchers.finishBroadcast(); 2471 } 2472 2473 private void dispatchNonDefaultDevice(int strategy, 2474 @NonNull List<AudioDeviceAttributes> devices) { 2475 final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast(); 2476 for (int i = 0; i < nbDispatchers; i++) { 2477 try { 2478 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged( 2479 strategy, devices); 2480 } catch (RemoteException e) { 2481 } 2482 } 2483 mNonDefDevDispatchers.finishBroadcast(); 2484 } 2485 2486 private void dispatchDevicesRoleForCapturePreset( 2487 int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { 2488 final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast(); 2489 for (int i = 0; i < nbDispatchers; ++i) { 2490 try { 2491 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged( 2492 capturePreset, role, devices); 2493 } catch (RemoteException e) { 2494 } 2495 } 2496 mDevRoleCapturePresetDispatchers.finishBroadcast(); 2497 } 2498 2499 @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { 2500 final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(), 2501 device.getAddress()); 2502 synchronized (mDevicesLock) { 2503 DeviceInfo di = mConnectedDevices.get(key); 2504 if (di == null) { 2505 return null; 2506 } 2507 return di.mSensorUuid; 2508 } 2509 } 2510 2511 /*package*/ String getDeviceSettings() { 2512 int deviceCatalogSize = 0; 2513 synchronized (mDeviceInventoryLock) { 2514 deviceCatalogSize = mDeviceInventory.size(); 2515 2516 final StringBuilder settingsBuilder = new StringBuilder( 2517 deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); 2518 2519 Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator(); 2520 if (iterator.hasNext()) { 2521 settingsBuilder.append(iterator.next().toPersistableString()); 2522 } 2523 while (iterator.hasNext()) { 2524 settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); 2525 settingsBuilder.append(iterator.next().toPersistableString()); 2526 } 2527 return settingsBuilder.toString(); 2528 } 2529 } 2530 2531 /*package*/ void setDeviceSettings(String settings) { 2532 clearDeviceInventory(); 2533 String[] devSettings = TextUtils.split(Objects.requireNonNull(settings), 2534 SETTING_DEVICE_SEPARATOR); 2535 // small list, not worth overhead of Arrays.stream(devSettings) 2536 for (String setting : devSettings) { 2537 AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting); 2538 // Note if the device is not compatible with spatialization mode or the device 2539 // type is not canonical, it will be ignored in {@link SpatializerHelper}. 2540 if (devState != null) { 2541 addOrUpdateDeviceSAStateInInventory(devState); 2542 addOrUpdateAudioDeviceCategoryInInventory(devState); 2543 } 2544 } 2545 } 2546 2547 //---------------------------------------------------------- 2548 // For tests only 2549 2550 /** 2551 * Check if device is in the list of connected devices 2552 * @param device 2553 * @return true if connected 2554 */ 2555 @VisibleForTesting 2556 public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { 2557 for (DeviceInfo di : getConnectedDevicesOfTypes( 2558 Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { 2559 if (di.mDeviceAddress.equals(device.getAddress())) { 2560 return true; 2561 } 2562 } 2563 return false; 2564 } 2565 } 2566