• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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