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