• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.media.AudioManager;
27 import android.media.MediaRoute2Info;
28 import android.media.MediaRoute2ProviderInfo;
29 import android.media.MediaRoute2ProviderService;
30 import android.media.MediaRouter2Utils;
31 import android.media.RouteDiscoveryPreference;
32 import android.media.RoutingSessionInfo;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.UserHandle;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.util.Slog;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.media.flags.Flags;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.Set;
48 
49 /**
50  * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
51  */
52 // TODO: check thread safety. We may need to use lock to protect variables.
53 class SystemMediaRoute2Provider extends MediaRoute2Provider {
54     // Package-visible to use this tag for all system routing logic (done across multiple classes).
55     /* package */ static final String TAG = "MR2SystemProvider";
56     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
57 
58     private static final ComponentName COMPONENT_NAME = new ComponentName(
59             SystemMediaRoute2Provider.class.getPackage().getName(),
60             SystemMediaRoute2Provider.class.getName());
61 
62     static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
63 
64     private final AudioManager mAudioManager;
65     private final Handler mHandler;
66     private final Context mContext;
67     private final UserHandle mUser;
68 
69     private final DeviceRouteController mDeviceRouteController;
70     private final BluetoothRouteController mBluetoothRouteController;
71 
72     private String mSelectedRouteId;
73     // For apps without MODIFYING_AUDIO_ROUTING permission.
74     // This should be the currently selected route.
75     MediaRoute2Info mDefaultRoute;
76     RoutingSessionInfo mDefaultSessionInfo;
77 
78     private final AudioManagerBroadcastReceiver mAudioReceiver =
79             new AudioManagerBroadcastReceiver();
80 
81     private final Object mRequestLock = new Object();
82 
83     @GuardedBy("mRequestLock")
84     private volatile SessionCreationOrTransferRequest mPendingSessionCreationOrTransferRequest;
85 
86     private final Object mTransferLock = new Object();
87 
88     @GuardedBy("mTransferLock")
89     @Nullable
90     private volatile SessionCreationOrTransferRequest mPendingTransferRequest;
91 
SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper)92     SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
93         super(COMPONENT_NAME);
94         mIsSystemRouteProvider = true;
95         mContext = context;
96         mUser = user;
97         mHandler = new Handler(looper);
98 
99         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
100 
101         mBluetoothRouteController =
102                 BluetoothRouteController.createInstance(
103                         context,
104                         () -> {
105                             publishProviderState();
106                             if (updateSessionInfosIfNeeded()) {
107                                 notifySessionInfoUpdated();
108                             }
109                         });
110 
111         mDeviceRouteController =
112                 DeviceRouteController.createInstance(
113                         context,
114                         looper,
115                         () ->
116                                 mHandler.post(
117                                         () -> {
118                                             publishProviderState();
119                                             if (updateSessionInfosIfNeeded()) {
120                                                 notifySessionInfoUpdated();
121                                             }
122                                         }));
123         updateProviderState();
124         updateSessionInfosIfNeeded();
125     }
126 
start()127     public void start() {
128         IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
129         intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
130         mContext.registerReceiverAsUser(mAudioReceiver, mUser,
131                 intentFilter, null, null);
132         mHandler.post(
133                 () -> {
134                     mDeviceRouteController.start(mUser);
135                     mBluetoothRouteController.start(mUser);
136                 });
137         updateVolume();
138     }
139 
stop()140     public void stop() {
141         mContext.unregisterReceiver(mAudioReceiver);
142         mHandler.post(
143                 () -> {
144                     mBluetoothRouteController.stop();
145                     mDeviceRouteController.stop();
146                     notifyProviderState();
147                 });
148     }
149 
150     @Override
setCallback(Callback callback)151     public void setCallback(Callback callback) {
152         super.setCallback(callback);
153         notifyProviderState();
154         notifySessionInfoUpdated();
155     }
156 
157     @Override
requestCreateSession( long requestId, String packageName, String routeOriginalId, Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)158     public void requestCreateSession(
159             long requestId,
160             String packageName,
161             String routeOriginalId,
162             Bundle sessionHints,
163             @RoutingSessionInfo.TransferReason int transferReason,
164             @NonNull UserHandle transferInitiatorUserHandle,
165             @NonNull String transferInitiatorPackageName) {
166         // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
167         // a route ID different from the default route ID. The service should've filtered.
168         if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
169             mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
170             return;
171         }
172 
173         if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
174             if (TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
175                 RoutingSessionInfo currentSessionInfo;
176                 synchronized (mLock) {
177                     currentSessionInfo = mSessionInfos.get(0);
178                 }
179                 mCallback.onSessionCreated(this, requestId, currentSessionInfo);
180                 return;
181             }
182         }
183 
184         synchronized (mRequestLock) {
185             // Handle the previous request as a failure if exists.
186             if (mPendingSessionCreationOrTransferRequest != null) {
187                 mCallback.onRequestFailed(
188                         /* provider= */ this,
189                         mPendingSessionCreationOrTransferRequest.mRequestId,
190                         MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
191             }
192             mPendingSessionCreationOrTransferRequest =
193                     new SessionCreationOrTransferRequest(
194                             requestId,
195                             routeOriginalId,
196                             RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
197                             transferInitiatorUserHandle,
198                             transferInitiatorPackageName);
199         }
200 
201         // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP.
202         transferToRoute(
203                 requestId,
204                 transferInitiatorUserHandle,
205                 transferInitiatorPackageName,
206                 SYSTEM_SESSION_ID,
207                 routeOriginalId,
208                 transferReason);
209     }
210 
211     @Override
releaseSession(long requestId, String sessionId)212     public void releaseSession(long requestId, String sessionId) {
213         // Do nothing
214     }
215 
216     @Override
updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)217     public void updateDiscoveryPreference(
218             Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference) {
219         // Do nothing
220     }
221 
222     @Override
selectRoute(long requestId, String sessionId, String routeId)223     public void selectRoute(long requestId, String sessionId, String routeId) {
224         // Do nothing since we don't support multiple BT yet.
225     }
226 
227     @Override
deselectRoute(long requestId, String sessionId, String routeId)228     public void deselectRoute(long requestId, String sessionId, String routeId) {
229         // Do nothing since we don't support multiple BT yet.
230     }
231 
232     @Override
transferToRoute( long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, String sessionOriginalId, String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason)233     public void transferToRoute(
234             long requestId,
235             @NonNull UserHandle transferInitiatorUserHandle,
236             @NonNull String transferInitiatorPackageName,
237             String sessionOriginalId,
238             String routeOriginalId,
239             @RoutingSessionInfo.TransferReason int transferReason) {
240         String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId();
241         if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
242             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
243                 // Transfer to the default route (which is the selected route). We replace the id to
244                 // be the selected route id so that the transfer reason gets updated.
245                 routeOriginalId = selectedDeviceRouteId;
246             } else {
247                 Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
248                 return;
249             }
250         }
251 
252         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
253             synchronized (mTransferLock) {
254                 mPendingTransferRequest =
255                         new SessionCreationOrTransferRequest(
256                                 requestId,
257                                 routeOriginalId,
258                                 transferReason,
259                                 transferInitiatorUserHandle,
260                                 transferInitiatorPackageName);
261             }
262         }
263 
264         String finalRouteId = routeOriginalId; // Make a final copy to use it in the lambda.
265         boolean isAvailableDeviceRoute =
266                 mDeviceRouteController.getAvailableRoutes().stream()
267                         .anyMatch(it -> it.getId().equals(finalRouteId));
268         boolean isSelectedDeviceRoute = TextUtils.equals(routeOriginalId, selectedDeviceRouteId);
269 
270         if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
271             // The requested route is managed by the device route controller. Note that the selected
272             // device route doesn't necessarily match mSelectedRouteId (which is the selected route
273             // of the routing session). If the selected device route is transferred to, we need to
274             // make the bluetooth routes inactive so that the device route becomes the selected
275             // route of the routing session.
276             mDeviceRouteController.transferTo(routeOriginalId);
277             mBluetoothRouteController.transferTo(null);
278         } else {
279             // The requested route is managed by the bluetooth route controller.
280             mDeviceRouteController.transferTo(null);
281             mBluetoothRouteController.transferTo(routeOriginalId);
282         }
283 
284         if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
285                 && updateSessionInfosIfNeeded()) {
286             notifySessionInfoUpdated();
287         }
288     }
289 
290     @Override
setRouteVolume(long requestId, String routeOriginalId, int volume)291     public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
292         if (!TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
293             return;
294         }
295         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
296     }
297 
298     @Override
setSessionVolume(long requestId, String sessionOriginalId, int volume)299     public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
300         // Do nothing since we don't support grouping volume yet.
301     }
302 
303     @Override
prepareReleaseSession(String sessionUniqueId)304     public void prepareReleaseSession(String sessionUniqueId) {
305         // Do nothing since the system session persists.
306     }
307 
getDefaultRoute()308     public MediaRoute2Info getDefaultRoute() {
309         return mDefaultRoute;
310     }
311 
getDefaultSessionInfo()312     public RoutingSessionInfo getDefaultSessionInfo() {
313         return mDefaultSessionInfo;
314     }
315 
316     /**
317      * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently
318      * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes
319      * set to the currently available (connected) bluetooth routes.
320      *
321      * <p>The session's client package name is set to the provided package name.
322      *
323      * <p>Returns {@code null} if there are no registered system sessions.
324      */
325     @Nullable
generateDeviceRouteSelectedSessionInfo(String packageName)326     public RoutingSessionInfo generateDeviceRouteSelectedSessionInfo(String packageName) {
327         synchronized (mLock) {
328             if (mSessionInfos.isEmpty()) {
329                 return null;
330             }
331 
332             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
333 
334             RoutingSessionInfo.Builder builder =
335                     new RoutingSessionInfo.Builder(SYSTEM_SESSION_ID, packageName)
336                             .setSystemSession(true);
337             builder.addSelectedRoute(selectedDeviceRoute.getId());
338             for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
339                 builder.addTransferableRoute(route.getId());
340             }
341 
342             if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
343                 for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
344                     if (!TextUtils.equals(selectedDeviceRoute.getId(), route.getId())) {
345                         builder.addTransferableRoute(route.getId());
346                     }
347                 }
348             }
349 
350             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
351                 RoutingSessionInfo oldSessionInfo = mSessionInfos.get(0);
352                 builder.setTransferReason(oldSessionInfo.getTransferReason())
353                         .setTransferInitiator(oldSessionInfo.getTransferInitiatorUserHandle(),
354                                 oldSessionInfo.getTransferInitiatorPackageName());
355             }
356 
357             return builder.setProviderId(mUniqueId).build();
358         }
359     }
360 
updateProviderState()361     private void updateProviderState() {
362         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
363 
364         // We must have a device route in the provider info.
365         if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
366             List<MediaRoute2Info> deviceRoutes = mDeviceRouteController.getAvailableRoutes();
367             for (MediaRoute2Info route : deviceRoutes) {
368                 builder.addRoute(route);
369             }
370             setProviderState(builder.build());
371         } else {
372             builder.addRoute(mDeviceRouteController.getSelectedRoute());
373         }
374 
375         for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
376             builder.addRoute(route);
377         }
378         MediaRoute2ProviderInfo providerInfo = builder.build();
379         setProviderState(providerInfo);
380         if (DEBUG) {
381             Slog.d(TAG, "Updating system provider info : " + providerInfo);
382         }
383     }
384 
385     /**
386      * Updates the mSessionInfo. Returns true if the session info is changed.
387      */
updateSessionInfosIfNeeded()388     boolean updateSessionInfosIfNeeded() {
389         synchronized (mLock) {
390             RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
391                     0);
392 
393             RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
394                     SYSTEM_SESSION_ID, "" /* clientPackageName */)
395                     .setSystemSession(true);
396 
397             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
398             MediaRoute2Info selectedRoute = selectedDeviceRoute;
399             MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
400             List<String> transferableRoutes = new ArrayList<>();
401 
402             if (selectedBtRoute != null) {
403                 selectedRoute = selectedBtRoute;
404                 transferableRoutes.add(selectedDeviceRoute.getId());
405             }
406             mSelectedRouteId = selectedRoute.getId();
407             mDefaultRoute =
408                     new MediaRoute2Info.Builder(MediaRoute2Info.ROUTE_ID_DEFAULT, selectedRoute)
409                             .setSystemRoute(true)
410                             .setProviderId(mUniqueId)
411                             .build();
412             builder.addSelectedRoute(mSelectedRouteId);
413             if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
414                 for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
415                     String routeId = route.getId();
416                     if (!mSelectedRouteId.equals(routeId)) {
417                         transferableRoutes.add(routeId);
418                     }
419                 }
420             }
421             for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
422                 transferableRoutes.add(route.getId());
423             }
424 
425             for (String route : transferableRoutes) {
426                 builder.addTransferableRoute(route);
427             }
428 
429             if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
430                 int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
431                 UserHandle transferInitiatorUserHandle = null;
432                 String transferInitiatorPackageName = null;
433 
434                 if (oldSessionInfo != null
435                         && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) {
436                     transferReason = oldSessionInfo.getTransferReason();
437                     transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
438                     transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
439                 }
440 
441                 synchronized (mTransferLock) {
442                     if (mPendingTransferRequest != null) {
443                         boolean isTransferringToTheSelectedRoute =
444                                 mPendingTransferRequest.isTargetRoute(selectedRoute);
445                         boolean canBePotentiallyTransferred =
446                                 mPendingTransferRequest.isTargetRouteIdInRouteOriginalIdList(
447                                         transferableRoutes);
448 
449                         if (isTransferringToTheSelectedRoute) {
450                             transferReason = mPendingTransferRequest.mTransferReason;
451                             transferInitiatorUserHandle =
452                                     mPendingTransferRequest.mTransferInitiatorUserHandle;
453                             transferInitiatorPackageName =
454                                     mPendingTransferRequest.mTransferInitiatorPackageName;
455 
456                             mPendingTransferRequest = null;
457                         } else if (!canBePotentiallyTransferred) {
458                             mPendingTransferRequest = null;
459                         }
460                     }
461                 }
462 
463                 builder.setTransferReason(transferReason)
464                         .setTransferInitiator(
465                                 transferInitiatorUserHandle, transferInitiatorPackageName);
466             }
467 
468             RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
469 
470             synchronized (mRequestLock) {
471                 reportPendingSessionRequestResultLockedIfNeeded(newSessionInfo);
472             }
473 
474             if (Objects.equals(oldSessionInfo, newSessionInfo)) {
475                 return false;
476             } else {
477                 if (DEBUG) {
478                     Slog.d(TAG, "Updating system routing session info : " + newSessionInfo);
479                 }
480                 mSessionInfos.clear();
481                 mSessionInfos.add(newSessionInfo);
482                 mDefaultSessionInfo =
483                         new RoutingSessionInfo.Builder(
484                                         SYSTEM_SESSION_ID, "" /* clientPackageName */)
485                                 .setProviderId(mUniqueId)
486                                 .setSystemSession(true)
487                                 .addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT)
488                                 .setTransferReason(newSessionInfo.getTransferReason())
489                                 .setTransferInitiator(
490                                         newSessionInfo.getTransferInitiatorUserHandle(),
491                                         newSessionInfo.getTransferInitiatorPackageName())
492                                 .build();
493                 return true;
494             }
495         }
496     }
497 
498     @GuardedBy("mRequestLock")
reportPendingSessionRequestResultLockedIfNeeded( RoutingSessionInfo newSessionInfo)499     private void reportPendingSessionRequestResultLockedIfNeeded(
500             RoutingSessionInfo newSessionInfo) {
501         if (mPendingSessionCreationOrTransferRequest == null) {
502             // No pending request, nothing to report.
503             return;
504         }
505 
506         long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId;
507         if (mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId.equals(
508                 mSelectedRouteId)) {
509             if (DEBUG) {
510                 Slog.w(
511                         TAG,
512                         "Session creation success to route "
513                                 + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
514             }
515             mPendingSessionCreationOrTransferRequest = null;
516             mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo);
517         } else {
518             boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute();
519             if (!Flags.enableWaitingStateForSystemSessionCreationRequest()
520                     || !isRequestedRouteConnectedBtRoute) {
521                 if (DEBUG) {
522                     Slog.w(
523                             TAG,
524                             "Session creation failed to route "
525                                     + mPendingSessionCreationOrTransferRequest
526                                             .mTargetOriginalRouteId);
527                 }
528                 mPendingSessionCreationOrTransferRequest = null;
529                 mCallback.onRequestFailed(
530                         this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
531             } else if (DEBUG) {
532                 Slog.w(
533                         TAG,
534                         "Session creation waiting state to route "
535                                 + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
536             }
537         }
538     }
539 
540     @GuardedBy("mRequestLock")
isRequestedRouteConnectedBtRoute()541     private boolean isRequestedRouteConnectedBtRoute() {
542         // Using AllRoutes instead of TransferableRoutes as BT Stack sends an intermediate update
543         // where two BT routes are active so the transferable routes list is empty.
544         // See b/307723189 for context
545         for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) {
546             if (TextUtils.equals(
547                     btRoute.getId(),
548                     mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId)) {
549                 return true;
550             }
551         }
552         return false;
553     }
554 
containsSelectedRouteWithId( @ullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId)555     private boolean containsSelectedRouteWithId(
556             @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) {
557         if (sessionInfo == null) {
558             return false;
559         }
560 
561         List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
562 
563         if (selectedRoutes.size() != 1) {
564             throw new IllegalStateException("Selected routes list should contain only 1 route id.");
565         }
566 
567         String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0));
568         return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId);
569     }
570 
publishProviderState()571     void publishProviderState() {
572         updateProviderState();
573         notifyProviderState();
574     }
575 
notifySessionInfoUpdated()576     void notifySessionInfoUpdated() {
577         if (mCallback == null) {
578             return;
579         }
580 
581         RoutingSessionInfo sessionInfo;
582         synchronized (mLock) {
583             sessionInfo = mSessionInfos.get(0);
584         }
585 
586         mCallback.onSessionUpdated(this, sessionInfo);
587     }
588 
589     @Override
getDebugString()590     protected String getDebugString() {
591         return TextUtils.formatSimple(
592                 "SystemMR2Provider - package: %s, selected route id: %s, bluetooth impl: %s",
593                 mComponentName.getPackageName(),
594                 mSelectedRouteId,
595                 mBluetoothRouteController.getClass().getSimpleName());
596     }
597 
updateVolume()598     void updateVolume() {
599         int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
600         int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
601 
602         if (mDefaultRoute.getVolume() != volume) {
603             mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
604                     .setVolume(volume)
605                     .build();
606         }
607 
608         if (mBluetoothRouteController.updateVolumeForDevices(devices, volume)) {
609             return;
610         }
611 
612         mDeviceRouteController.updateVolume(volume);
613 
614         publishProviderState();
615     }
616 
617     private class AudioManagerBroadcastReceiver extends BroadcastReceiver {
618         // This will be called in the main thread.
619         @Override
onReceive(Context context, Intent intent)620         public void onReceive(Context context, Intent intent) {
621             if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)
622                     && !intent.getAction().equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
623                 return;
624             }
625 
626             int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
627             if (streamType != AudioManager.STREAM_MUSIC) {
628                 return;
629             }
630 
631             if (Flags.enableMr2ServiceNonMainBgThread()) {
632                 mHandler.post(SystemMediaRoute2Provider.this::updateVolume);
633             } else {
634                 updateVolume();
635             }
636         }
637     }
638 }
639