1 /* 2 * Copyright (C) 2023 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 17 package com.android.server.media; 18 19 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; 20 import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; 21 22 import android.Manifest; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.bluetooth.BluetoothAdapter; 27 import android.bluetooth.BluetoothDevice; 28 import android.content.Context; 29 import android.media.AudioAttributes; 30 import android.media.AudioDeviceAttributes; 31 import android.media.AudioDeviceCallback; 32 import android.media.AudioDeviceInfo; 33 import android.media.AudioManager; 34 import android.media.MediaRoute2Info; 35 import android.media.audiopolicy.AudioProductStrategy; 36 import android.os.Handler; 37 import android.os.HandlerExecutor; 38 import android.os.Looper; 39 import android.os.UserHandle; 40 import android.text.TextUtils; 41 import android.util.Slog; 42 import android.util.SparseArray; 43 44 import com.android.internal.R; 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController; 47 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Objects; 52 53 /** 54 * Maintains a list of all available routes and supports transfers to any of them. 55 * 56 * <p>This implementation is intended for use in conjunction with {@link 57 * NoOpBluetoothRouteController}, as it manages bluetooth devices directly. 58 * 59 * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the 60 * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes 61 * which are managed by {@link BluetoothDeviceRoutesManager}, which depends on the bluetooth stack 62 * ({@link BluetoothAdapter} and related classes). 63 * 64 * <p>This class runs as part of the system_server process, but depends on classes that may 65 * communicate with other processes, like bluetooth or audio server. And these other processes may 66 * require binder threads from system server. As a result, there are a few threading considerations 67 * to keep in mind: 68 * 69 * <ul> 70 * <li>Some of this class' internal state is synchronized using {@code this} as lock. 71 * <li>Binder threads may call into this class and run synchronized code. 72 * <li>As a result the above, in order to avoid deadlocks, calls to components that may call into 73 * other processes (like {@link AudioManager} or {@link BluetoothDeviceRoutesManager}) must 74 * not be synchronized nor occur on a binder thread. 75 * </ul> 76 */ 77 /* package */ final class AudioManagerRouteController implements DeviceRouteController { 78 private static final String TAG = SystemMediaRoute2Provider.TAG; 79 80 @NonNull 81 private static final AudioAttributes MEDIA_USAGE_AUDIO_ATTRIBUTES = 82 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); 83 84 @NonNull 85 private static final SparseArray<SystemRouteInfo> AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO = 86 new SparseArray<>(); 87 88 @NonNull private final Context mContext; 89 @NonNull private final AudioManager mAudioManager; 90 @NonNull private final Handler mHandler; 91 @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; 92 @NonNull private final BluetoothDeviceRoutesManager mBluetoothRouteController; 93 94 @NonNull private final AudioProductStrategy mStrategyForMedia; 95 96 @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl(); 97 98 @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus; 99 100 @NonNull 101 private final AudioManager.OnDevicesForAttributesChangedListener 102 mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener; 103 104 @GuardedBy("this") 105 @NonNull 106 private final Map<String, MediaRoute2InfoHolder> mRouteIdToAvailableDeviceRoutes = 107 new HashMap<>(); 108 109 @GuardedBy("this") 110 @NonNull 111 private MediaRoute2Info mSelectedRoute; 112 113 // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means 114 // no support for transferring to inactive bluetooth routes and transferring to any routes 115 // respectively. 116 @RequiresPermission( 117 anyOf = { 118 Manifest.permission.MODIFY_AUDIO_ROUTING, 119 Manifest.permission.QUERY_AUDIO_STATE 120 }) AudioManagerRouteController( @onNull Context context, @NonNull AudioManager audioManager, @NonNull Looper looper, @NonNull AudioProductStrategy strategyForMedia, @NonNull BluetoothAdapter btAdapter, @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener)121 /* package */ AudioManagerRouteController( 122 @NonNull Context context, 123 @NonNull AudioManager audioManager, 124 @NonNull Looper looper, 125 @NonNull AudioProductStrategy strategyForMedia, 126 @NonNull BluetoothAdapter btAdapter, 127 @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { 128 mContext = Objects.requireNonNull(context); 129 mAudioManager = Objects.requireNonNull(audioManager); 130 mHandler = new Handler(Objects.requireNonNull(looper)); 131 mStrategyForMedia = Objects.requireNonNull(strategyForMedia); 132 mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener); 133 134 mBuiltInSpeakerSuitabilityStatus = 135 DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext); 136 137 mBluetoothRouteController = 138 new BluetoothDeviceRoutesManager( 139 mContext, mHandler, btAdapter, this::rebuildAvailableRoutesAndNotify); 140 // Just build routes but don't notify. The caller may not expect the listener to be invoked 141 // before this constructor has finished executing. 142 rebuildAvailableRoutes(); 143 } 144 145 @RequiresPermission( 146 anyOf = { 147 Manifest.permission.MODIFY_AUDIO_ROUTING, 148 Manifest.permission.QUERY_AUDIO_STATE 149 }) 150 @Override start(UserHandle mUser)151 public void start(UserHandle mUser) { 152 mBluetoothRouteController.start(mUser); 153 mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler); 154 mAudioManager.addOnDevicesForAttributesChangedListener( 155 AudioRoutingUtils.ATTRIBUTES_MEDIA, 156 new HandlerExecutor(mHandler), 157 mOnDevicesForAttributesChangedListener); 158 } 159 160 @RequiresPermission( 161 anyOf = { 162 Manifest.permission.MODIFY_AUDIO_ROUTING, 163 Manifest.permission.QUERY_AUDIO_STATE 164 }) 165 @Override stop()166 public void stop() { 167 mAudioManager.removeOnDevicesForAttributesChangedListener( 168 mOnDevicesForAttributesChangedListener); 169 mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); 170 mBluetoothRouteController.stop(); 171 mHandler.removeCallbacksAndMessages(/* token= */ null); 172 } 173 174 @Override 175 @NonNull getSelectedRoute()176 public synchronized MediaRoute2Info getSelectedRoute() { 177 return mSelectedRoute; 178 } 179 180 @Override 181 @NonNull getAvailableRoutes()182 public synchronized List<MediaRoute2Info> getAvailableRoutes() { 183 return mRouteIdToAvailableDeviceRoutes.values().stream() 184 .map(it -> it.mMediaRoute2Info) 185 .toList(); 186 } 187 188 @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) 189 @Override transferTo(@ullable String routeId)190 public void transferTo(@Nullable String routeId) { 191 if (routeId == null) { 192 // This should never happen: This branch should only execute when the matching bluetooth 193 // route controller is not the no-op one. 194 // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the 195 // legacy route controller implementations. 196 Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)"); 197 return; 198 } 199 MediaRoute2InfoHolder mediaRoute2InfoHolder; 200 synchronized (this) { 201 mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId); 202 } 203 if (mediaRoute2InfoHolder == null) { 204 Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId); 205 return; 206 } 207 Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder); 208 Runnable guardedTransferAction = 209 () -> { 210 try { 211 transferAction.run(); 212 } catch (Throwable throwable) { 213 // We swallow the exception to avoid crashing system_server, since this 214 // doesn't run on a binder thread. 215 Slog.e( 216 TAG, 217 "Unexpected exception while transferring to route id: " + routeId, 218 throwable); 219 mHandler.post(this::rebuildAvailableRoutesAndNotify); 220 } 221 }; 222 // We post the transfer operation to the handler to avoid making these calls on a binder 223 // thread. See class javadoc for details. 224 mHandler.post(guardedTransferAction); 225 } 226 227 @RequiresPermission( 228 anyOf = { 229 Manifest.permission.MODIFY_AUDIO_ROUTING, 230 Manifest.permission.QUERY_AUDIO_STATE 231 }) 232 @Override updateVolume(int volume)233 public boolean updateVolume(int volume) { 234 // TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We 235 // don't need to rebuild all available routes. 236 rebuildAvailableRoutesAndNotify(); 237 return true; 238 } 239 getTransferActionForRoute(MediaRoute2InfoHolder mediaRoute2InfoHolder)240 private Runnable getTransferActionForRoute(MediaRoute2InfoHolder mediaRoute2InfoHolder) { 241 if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) { 242 String deviceAddress = mediaRoute2InfoHolder.mMediaRoute2Info.getAddress(); 243 return () -> { 244 // By default, the last connected device is the active route so we don't 245 // need to apply a routing audio policy. 246 mBluetoothRouteController.activateBluetoothDeviceWithAddress(deviceAddress); 247 mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); 248 }; 249 250 } else { 251 AudioDeviceAttributes deviceAttributes = 252 new AudioDeviceAttributes( 253 AudioDeviceAttributes.ROLE_OUTPUT, 254 mediaRoute2InfoHolder.mAudioDeviceInfoType, 255 /* address= */ ""); // This is not a BT device, hence no address needed. 256 return () -> 257 mAudioManager.setPreferredDeviceForStrategy( 258 mStrategyForMedia, deviceAttributes); 259 } 260 } 261 262 @RequiresPermission( 263 anyOf = { 264 Manifest.permission.MODIFY_AUDIO_ROUTING, 265 Manifest.permission.QUERY_AUDIO_STATE 266 }) onDevicesForAttributesChangedListener( AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes)267 private void onDevicesForAttributesChangedListener( 268 AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) { 269 if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) { 270 // We only care about the media usage. Ignore everything else. 271 rebuildAvailableRoutesAndNotify(); 272 } 273 } 274 275 @RequiresPermission( 276 anyOf = { 277 Manifest.permission.MODIFY_AUDIO_ROUTING, 278 Manifest.permission.QUERY_AUDIO_STATE 279 }) rebuildAvailableRoutesAndNotify()280 private void rebuildAvailableRoutesAndNotify() { 281 rebuildAvailableRoutes(); 282 mOnDeviceRouteChangedListener.onDeviceRouteChanged(); 283 } 284 285 @RequiresPermission( 286 anyOf = { 287 Manifest.permission.MODIFY_AUDIO_ROUTING, 288 Manifest.permission.QUERY_AUDIO_STATE 289 }) rebuildAvailableRoutes()290 private void rebuildAvailableRoutes() { 291 List<AudioDeviceAttributes> attributesOfSelectedOutputDevices = 292 mAudioManager.getDevicesForAttributes(MEDIA_USAGE_AUDIO_ATTRIBUTES); 293 int selectedDeviceAttributesType; 294 if (attributesOfSelectedOutputDevices.isEmpty()) { 295 Slog.e( 296 TAG, 297 "Unexpected empty list of output devices for media. Using built-in speakers."); 298 selectedDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 299 } else { 300 if (attributesOfSelectedOutputDevices.size() > 1) { 301 Slog.w( 302 TAG, 303 "AudioManager.getDevicesForAttributes returned more than one element. Using" 304 + " the first one."); 305 } 306 selectedDeviceAttributesType = attributesOfSelectedOutputDevices.get(0).getType(); 307 } 308 309 updateAvailableRoutes( 310 selectedDeviceAttributesType, 311 /* audioDeviceInfos= */ mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS), 312 /* availableBluetoothRoutes= */ mBluetoothRouteController 313 .getAvailableBluetoothRoutes(), 314 /* musicVolume= */ mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC), 315 /* musicMaxVolume= */ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 316 /* isVolumeFixed= */ mAudioManager.isVolumeFixed()); 317 } 318 319 /** 320 * Updates route and session info using the given information from {@link AudioManager}. 321 * 322 * <p>Synchronization is limited to this method in order to avoid calling into {@link 323 * AudioManager} or {@link BluetoothDeviceRoutesManager} while holding a lock that may also be 324 * acquired by binder threads. See class javadoc for more details. 325 * 326 * @param selectedDeviceAttributesType The {@link AudioDeviceInfo#getType() type} that 327 * corresponds to the currently selected route. 328 * @param audioDeviceInfos The available audio outputs as obtained from {@link 329 * AudioManager#getDevices}. 330 * @param availableBluetoothRoutes The available bluetooth routes as obtained from {@link 331 * BluetoothDeviceRoutesManager#getAvailableBluetoothRoutes()}. 332 * @param musicVolume The volume of the music stream as obtained from {@link 333 * AudioManager#getStreamVolume}. 334 * @param musicMaxVolume The max volume of the music stream as obtained from {@link 335 * AudioManager#getStreamMaxVolume}. 336 * @param isVolumeFixed Whether the volume is fixed as obtained from {@link 337 * AudioManager#isVolumeFixed()}. 338 */ updateAvailableRoutes( int selectedDeviceAttributesType, AudioDeviceInfo[] audioDeviceInfos, List<MediaRoute2Info> availableBluetoothRoutes, int musicVolume, int musicMaxVolume, boolean isVolumeFixed)339 private synchronized void updateAvailableRoutes( 340 int selectedDeviceAttributesType, 341 AudioDeviceInfo[] audioDeviceInfos, 342 List<MediaRoute2Info> availableBluetoothRoutes, 343 int musicVolume, 344 int musicMaxVolume, 345 boolean isVolumeFixed) { 346 mRouteIdToAvailableDeviceRoutes.clear(); 347 MediaRoute2InfoHolder newSelectedRouteHolder = null; 348 for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) { 349 MediaRoute2Info mediaRoute2Info = 350 createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo); 351 // Null means audioDeviceInfo is not a supported media output, like a phone's builtin 352 // earpiece. We ignore those. 353 if (mediaRoute2Info != null) { 354 int audioDeviceInfoType = audioDeviceInfo.getType(); 355 MediaRoute2InfoHolder newHolder = 356 MediaRoute2InfoHolder.createForAudioManagerRoute( 357 mediaRoute2Info, audioDeviceInfoType); 358 mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder); 359 if (selectedDeviceAttributesType == audioDeviceInfoType) { 360 newSelectedRouteHolder = newHolder; 361 } 362 } 363 } 364 365 if (mRouteIdToAvailableDeviceRoutes.isEmpty()) { 366 // Due to an unknown reason (possibly an audio server crash), we ended up with an empty 367 // list of routes. Our entire codebase assumes at least one system route always exists, 368 // so we create a placeholder route represented as a built-in speaker for 369 // user-presentation purposes. 370 Slog.e(TAG, "Ended up with an empty list of routes. Creating a placeholder route."); 371 MediaRoute2InfoHolder placeholderRouteHolder = createPlaceholderBuiltinSpeakerRoute(); 372 String placeholderRouteId = placeholderRouteHolder.mMediaRoute2Info.getId(); 373 mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder); 374 } 375 376 if (newSelectedRouteHolder == null) { 377 Slog.e( 378 TAG, 379 "Could not map this selected device attribute type to an available route: " 380 + selectedDeviceAttributesType); 381 // We know mRouteIdToAvailableDeviceRoutes is not empty. 382 newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next(); 383 } 384 MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo = 385 newSelectedRouteHolder.copyWithVolumeInfo( 386 musicVolume, musicMaxVolume, isVolumeFixed); 387 mRouteIdToAvailableDeviceRoutes.put( 388 newSelectedRouteHolder.mMediaRoute2Info.getId(), 389 selectedRouteHolderWithUpdatedVolumeInfo); 390 mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info; 391 392 // We only add those BT routes that we have not already obtained from audio manager (which 393 // are active). 394 availableBluetoothRoutes.stream() 395 .filter(it -> !mRouteIdToAvailableDeviceRoutes.containsKey(it.getId())) 396 .map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute) 397 .forEach( 398 it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it)); 399 } 400 createPlaceholderBuiltinSpeakerRoute()401 private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() { 402 int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 403 return MediaRoute2InfoHolder.createForAudioManagerRoute( 404 createMediaRoute2Info( 405 /* routeId= */ null, type, /* productName= */ null, /* address= */ null), 406 type); 407 } 408 409 @Nullable createMediaRoute2InfoFromAudioDeviceInfo( AudioDeviceInfo audioDeviceInfo)410 private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo( 411 AudioDeviceInfo audioDeviceInfo) { 412 String address = audioDeviceInfo.getAddress(); 413 414 // Passing a null route id means we want to get the default id for the route. Generally, we 415 // only expect to pass null for non-Bluetooth routes. 416 String routeId = null; 417 418 // We use the name from the port instead AudioDeviceInfo#getProductName because the latter 419 // replaces empty names with the name of the device (example: Pixel 8). In that case we want 420 // to derive a name ourselves from the type instead. 421 String deviceName = audioDeviceInfo.getPort().name(); 422 423 if (!TextUtils.isEmpty(address)) { 424 routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address); 425 deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address); 426 } 427 return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address); 428 } 429 430 /** 431 * Creates a new {@link MediaRoute2Info} using the provided information. 432 * 433 * @param routeId A route id, or null to use an id pre-defined for the given {@code type}. 434 * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}. 435 * @param deviceName A human readable name to populate the route's {@link 436 * MediaRoute2Info#getName name}, or null to use a predefined name for the given {@code 437 * type}. 438 * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link 439 * BluetoothDevice#getAddress()}. 440 * @return The new {@link MediaRoute2Info}. 441 */ 442 @Nullable createMediaRoute2Info( @ullable String routeId, int audioDeviceInfoType, @Nullable CharSequence deviceName, @Nullable String address)443 private MediaRoute2Info createMediaRoute2Info( 444 @Nullable String routeId, 445 int audioDeviceInfoType, 446 @Nullable CharSequence deviceName, 447 @Nullable String address) { 448 SystemRouteInfo systemRouteInfo = 449 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType); 450 if (systemRouteInfo == null) { 451 // Device type that's intentionally unsupported for media output, like the built-in 452 // earpiece. 453 return null; 454 } 455 CharSequence humanReadableName = deviceName; 456 if (TextUtils.isEmpty(humanReadableName)) { 457 humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource); 458 } 459 if (routeId == null) { 460 // The caller hasn't provided an id, so we use a pre-defined one. This happens when we 461 // are creating a non-BT route, or we are creating a BT route but a race condition 462 // caused AudioManager to expose the BT route before BluetoothAdapter, preventing us 463 // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress. 464 routeId = systemRouteInfo.mDefaultRouteId; 465 } 466 MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName) 467 .setType(systemRouteInfo.mMediaRoute2InfoType) 468 .setAddress(address) 469 .setSystemRoute(true) 470 .addFeature(FEATURE_LIVE_AUDIO) 471 .addFeature(FEATURE_LOCAL_PLAYBACK) 472 .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED); 473 474 if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) { 475 builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus); 476 } 477 478 return builder.build(); 479 } 480 481 /** 482 * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the 483 * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this 484 * class. 485 */ 486 private static class MediaRoute2InfoHolder { 487 488 public final MediaRoute2Info mMediaRoute2Info; 489 public final int mAudioDeviceInfoType; 490 public final boolean mCorrespondsToInactiveBluetoothRoute; 491 createForAudioManagerRoute( MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType)492 public static MediaRoute2InfoHolder createForAudioManagerRoute( 493 MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) { 494 return new MediaRoute2InfoHolder( 495 mediaRoute2Info, 496 audioDeviceInfoType, 497 /* correspondsToInactiveBluetoothRoute= */ false); 498 } 499 createForInactiveBluetoothRoute( MediaRoute2Info mediaRoute2Info)500 public static MediaRoute2InfoHolder createForInactiveBluetoothRoute( 501 MediaRoute2Info mediaRoute2Info) { 502 // There's no corresponding audio device info, hence the audio device info type is 503 // unknown. 504 return new MediaRoute2InfoHolder( 505 mediaRoute2Info, 506 /* audioDeviceInfoType= */ AudioDeviceInfo.TYPE_UNKNOWN, 507 /* correspondsToInactiveBluetoothRoute= */ true); 508 } 509 MediaRoute2InfoHolder( MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType, boolean correspondsToInactiveBluetoothRoute)510 private MediaRoute2InfoHolder( 511 MediaRoute2Info mediaRoute2Info, 512 int audioDeviceInfoType, 513 boolean correspondsToInactiveBluetoothRoute) { 514 mMediaRoute2Info = mediaRoute2Info; 515 mAudioDeviceInfoType = audioDeviceInfoType; 516 mCorrespondsToInactiveBluetoothRoute = correspondsToInactiveBluetoothRoute; 517 } 518 copyWithVolumeInfo( int musicVolume, int musicMaxVolume, boolean isVolumeFixed)519 public MediaRoute2InfoHolder copyWithVolumeInfo( 520 int musicVolume, int musicMaxVolume, boolean isVolumeFixed) { 521 MediaRoute2Info routeInfoWithVolumeInfo = 522 new MediaRoute2Info.Builder(mMediaRoute2Info) 523 .setVolumeHandling( 524 isVolumeFixed 525 ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED 526 : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) 527 .setVolume(musicVolume) 528 .setVolumeMax(musicMaxVolume) 529 .build(); 530 return new MediaRoute2InfoHolder( 531 routeInfoWithVolumeInfo, 532 mAudioDeviceInfoType, 533 mCorrespondsToInactiveBluetoothRoute); 534 } 535 } 536 537 /** 538 * Holds route information about an {@link AudioDeviceInfo#getType() audio device info type}. 539 */ 540 private static class SystemRouteInfo { 541 /** The type to use for {@link MediaRoute2Info#getType()}. */ 542 public final int mMediaRoute2InfoType; 543 544 /** 545 * Holds the route id to use if no other id is provided. 546 * 547 * <p>We only expect this id to be used for non-bluetooth routes. For bluetooth routes, in a 548 * normal scenario, the id is generated from the device information (like address, or 549 * hiSyncId), and this value is ignored. A non-normal scenario may occur when there's race 550 * condition between {@link BluetoothAdapter} and {@link AudioManager}, who are not 551 * synchronized. 552 */ 553 public final String mDefaultRouteId; 554 555 /** 556 * The name to use for {@link MediaRoute2Info#getName()}. 557 * 558 * <p>Usually replaced by the UI layer with a localized string. 559 */ 560 public final int mNameResource; 561 SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource)562 private SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource) { 563 mMediaRoute2InfoType = mediaRoute2InfoType; 564 mDefaultRouteId = defaultRouteId; 565 mNameResource = nameResource; 566 } 567 } 568 569 private class AudioDeviceCallbackImpl extends AudioDeviceCallback { 570 @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) 571 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)572 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 573 for (AudioDeviceInfo deviceInfo : addedDevices) { 574 if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) { 575 // When a new valid media output is connected, we clear any routing policies so 576 // that the default routing logic from the audio framework kicks in. As a result 577 // of this, when the user connects a bluetooth device or a wired headset, the 578 // new device becomes the active route, which is the traditional behavior. 579 mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia); 580 rebuildAvailableRoutesAndNotify(); 581 break; 582 } 583 } 584 } 585 586 @RequiresPermission( 587 anyOf = { 588 Manifest.permission.MODIFY_AUDIO_ROUTING, 589 Manifest.permission.QUERY_AUDIO_STATE 590 }) 591 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)592 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 593 for (AudioDeviceInfo deviceInfo : removedDevices) { 594 if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) { 595 rebuildAvailableRoutesAndNotify(); 596 break; 597 } 598 } 599 } 600 } 601 602 static { AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, new SystemRouteInfo( MediaRoute2Info.TYPE_BUILTIN_SPEAKER, "ROUTE_ID_BUILTIN_SPEAKER", R.string.default_audio_route_name))603 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 604 AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, 605 new SystemRouteInfo( 606 MediaRoute2Info.TYPE_BUILTIN_SPEAKER, 607 /* defaultRouteId= */ "ROUTE_ID_BUILTIN_SPEAKER", 608 /* nameResource= */ R.string.default_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_WIRED_HEADSET, new SystemRouteInfo( MediaRoute2Info.TYPE_WIRED_HEADSET, "ROUTE_ID_WIRED_HEADSET", R.string.default_audio_route_name_headphones))609 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 610 AudioDeviceInfo.TYPE_WIRED_HEADSET, 611 new SystemRouteInfo( 612 MediaRoute2Info.TYPE_WIRED_HEADSET, 613 /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADSET", 614 /* nameResource= */ R.string.default_audio_route_name_headphones)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_WIRED_HEADPHONES, new SystemRouteInfo( MediaRoute2Info.TYPE_WIRED_HEADPHONES, "ROUTE_ID_WIRED_HEADPHONES", R.string.default_audio_route_name_headphones))615 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 616 AudioDeviceInfo.TYPE_WIRED_HEADPHONES, 617 new SystemRouteInfo( 618 MediaRoute2Info.TYPE_WIRED_HEADPHONES, 619 /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADPHONES", 620 /* nameResource= */ R.string.default_audio_route_name_headphones)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, new SystemRouteInfo( MediaRoute2Info.TYPE_BLUETOOTH_A2DP, "ROUTE_ID_BLUETOOTH_A2DP", R.string.bluetooth_a2dp_audio_route_name))621 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 622 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, 623 new SystemRouteInfo( 624 MediaRoute2Info.TYPE_BLUETOOTH_A2DP, 625 /* defaultRouteId= */ "ROUTE_ID_BLUETOOTH_A2DP", 626 /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_HDMI, new SystemRouteInfo( MediaRoute2Info.TYPE_HDMI, "ROUTE_ID_HDMI", R.string.default_audio_route_name_external_device))627 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 628 AudioDeviceInfo.TYPE_HDMI, 629 new SystemRouteInfo( 630 MediaRoute2Info.TYPE_HDMI, 631 /* defaultRouteId= */ "ROUTE_ID_HDMI", 632 /* nameResource= */ R.string.default_audio_route_name_external_device)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_DOCK, new SystemRouteInfo( MediaRoute2Info.TYPE_DOCK, "ROUTE_ID_DOCK", R.string.default_audio_route_name_dock_speakers))633 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 634 AudioDeviceInfo.TYPE_DOCK, 635 new SystemRouteInfo( 636 MediaRoute2Info.TYPE_DOCK, 637 /* defaultRouteId= */ "ROUTE_ID_DOCK", 638 /* nameResource= */ R.string.default_audio_route_name_dock_speakers)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_USB_DEVICE, new SystemRouteInfo( MediaRoute2Info.TYPE_USB_DEVICE, "ROUTE_ID_USB_DEVICE", R.string.default_audio_route_name_usb))639 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 640 AudioDeviceInfo.TYPE_USB_DEVICE, 641 new SystemRouteInfo( 642 MediaRoute2Info.TYPE_USB_DEVICE, 643 /* defaultRouteId= */ "ROUTE_ID_USB_DEVICE", 644 /* nameResource= */ R.string.default_audio_route_name_usb)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_USB_HEADSET, new SystemRouteInfo( MediaRoute2Info.TYPE_USB_HEADSET, "ROUTE_ID_USB_HEADSET", R.string.default_audio_route_name_usb))645 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 646 AudioDeviceInfo.TYPE_USB_HEADSET, 647 new SystemRouteInfo( 648 MediaRoute2Info.TYPE_USB_HEADSET, 649 /* defaultRouteId= */ "ROUTE_ID_USB_HEADSET", 650 /* nameResource= */ R.string.default_audio_route_name_usb)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_HDMI_ARC, new SystemRouteInfo( MediaRoute2Info.TYPE_HDMI_ARC, "ROUTE_ID_HDMI_ARC", R.string.default_audio_route_name_external_device))651 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 652 AudioDeviceInfo.TYPE_HDMI_ARC, 653 new SystemRouteInfo( 654 MediaRoute2Info.TYPE_HDMI_ARC, 655 /* defaultRouteId= */ "ROUTE_ID_HDMI_ARC", 656 /* nameResource= */ R.string.default_audio_route_name_external_device)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_HDMI_EARC, new SystemRouteInfo( MediaRoute2Info.TYPE_HDMI_EARC, "ROUTE_ID_HDMI_EARC", R.string.default_audio_route_name_external_device))657 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 658 AudioDeviceInfo.TYPE_HDMI_EARC, 659 new SystemRouteInfo( 660 MediaRoute2Info.TYPE_HDMI_EARC, 661 /* defaultRouteId= */ "ROUTE_ID_HDMI_EARC", 662 /* nameResource= */ R.string.default_audio_route_name_external_device)); 663 // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE, 664 // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID. AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_HEARING_AID, new SystemRouteInfo( MediaRoute2Info.TYPE_HEARING_AID, "ROUTE_ID_HEARING_AID", R.string.bluetooth_a2dp_audio_route_name))665 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 666 AudioDeviceInfo.TYPE_HEARING_AID, 667 new SystemRouteInfo( 668 MediaRoute2Info.TYPE_HEARING_AID, 669 /* defaultRouteId= */ "ROUTE_ID_HEARING_AID", 670 /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_BLE_HEADSET, new SystemRouteInfo( MediaRoute2Info.TYPE_BLE_HEADSET, "ROUTE_ID_BLE_HEADSET", R.string.bluetooth_a2dp_audio_route_name))671 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 672 AudioDeviceInfo.TYPE_BLE_HEADSET, 673 new SystemRouteInfo( 674 MediaRoute2Info.TYPE_BLE_HEADSET, 675 /* defaultRouteId= */ "ROUTE_ID_BLE_HEADSET", 676 /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_BLE_SPEAKER, new SystemRouteInfo( MediaRoute2Info.TYPE_BLE_HEADSET, "ROUTE_ID_BLE_SPEAKER", R.string.bluetooth_a2dp_audio_route_name))677 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 678 AudioDeviceInfo.TYPE_BLE_SPEAKER, 679 new SystemRouteInfo( 680 MediaRoute2Info.TYPE_BLE_HEADSET, // TODO: b/305199571 - Make a new type. 681 /* defaultRouteId= */ "ROUTE_ID_BLE_SPEAKER", 682 /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_BLE_BROADCAST, new SystemRouteInfo( MediaRoute2Info.TYPE_BLE_HEADSET, "ROUTE_ID_BLE_BROADCAST", R.string.bluetooth_a2dp_audio_route_name))683 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 684 AudioDeviceInfo.TYPE_BLE_BROADCAST, 685 new SystemRouteInfo( 686 MediaRoute2Info.TYPE_BLE_HEADSET, 687 /* defaultRouteId= */ "ROUTE_ID_BLE_BROADCAST", 688 /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_LINE_DIGITAL, new SystemRouteInfo( MediaRoute2Info.TYPE_UNKNOWN, "ROUTE_ID_LINE_DIGITAL", R.string.default_audio_route_name_external_device))689 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 690 AudioDeviceInfo.TYPE_LINE_DIGITAL, 691 new SystemRouteInfo( 692 MediaRoute2Info.TYPE_UNKNOWN, 693 /* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL", 694 /* nameResource= */ R.string.default_audio_route_name_external_device)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_LINE_ANALOG, new SystemRouteInfo( MediaRoute2Info.TYPE_UNKNOWN, "ROUTE_ID_LINE_ANALOG", R.string.default_audio_route_name_external_device))695 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 696 AudioDeviceInfo.TYPE_LINE_ANALOG, 697 new SystemRouteInfo( 698 MediaRoute2Info.TYPE_UNKNOWN, 699 /* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG", 700 /* nameResource= */ R.string.default_audio_route_name_external_device)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_AUX_LINE, new SystemRouteInfo( MediaRoute2Info.TYPE_UNKNOWN, "ROUTE_ID_AUX_LINE", R.string.default_audio_route_name_external_device))701 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 702 AudioDeviceInfo.TYPE_AUX_LINE, 703 new SystemRouteInfo( 704 MediaRoute2Info.TYPE_UNKNOWN, 705 /* defaultRouteId= */ "ROUTE_ID_AUX_LINE", 706 /* nameResource= */ R.string.default_audio_route_name_external_device)); AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( AudioDeviceInfo.TYPE_DOCK_ANALOG, new SystemRouteInfo( MediaRoute2Info.TYPE_DOCK, "ROUTE_ID_DOCK_ANALOG", R.string.default_audio_route_name_dock_speakers))707 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put( 708 AudioDeviceInfo.TYPE_DOCK_ANALOG, 709 new SystemRouteInfo( 710 MediaRoute2Info.TYPE_DOCK, 711 /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG", 712 /* nameResource= */ R.string.default_audio_route_name_dock_speakers)); 713 } 714 } 715