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