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