1 /*
2  * Copyright (C) 2013 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.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
20 
21 import android.Manifest;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.app.ActivityManager;
26 import android.app.UserSwitchObserver;
27 import android.bluetooth.BluetoothA2dp;
28 import android.bluetooth.BluetoothDevice;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.pm.PackageManager;
34 import android.content.res.Resources;
35 import android.media.AudioPlaybackConfiguration;
36 import android.media.AudioRoutesInfo;
37 import android.media.AudioSystem;
38 import android.media.IAudioRoutesObserver;
39 import android.media.IAudioService;
40 import android.media.IMediaRouter2;
41 import android.media.IMediaRouter2Manager;
42 import android.media.IMediaRouterClient;
43 import android.media.IMediaRouterService;
44 import android.media.MediaRoute2Info;
45 import android.media.MediaRouter;
46 import android.media.MediaRouterClientState;
47 import android.media.RemoteDisplayState;
48 import android.media.RemoteDisplayState.RemoteDisplayInfo;
49 import android.media.RouteDiscoveryPreference;
50 import android.media.RouteListingPreference;
51 import android.media.RoutingSessionInfo;
52 import android.os.Binder;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.IBinder;
56 import android.os.Looper;
57 import android.os.Message;
58 import android.os.RemoteException;
59 import android.os.ServiceManager;
60 import android.os.SystemClock;
61 import android.os.UserHandle;
62 import android.text.TextUtils;
63 import android.util.ArrayMap;
64 import android.util.IntArray;
65 import android.util.Log;
66 import android.util.Slog;
67 import android.util.SparseArray;
68 import android.util.TimeUtils;
69 
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.util.DumpUtils;
72 import com.android.server.LocalServices;
73 import com.android.server.Watchdog;
74 import com.android.server.pm.UserManagerInternal;
75 import com.android.server.statusbar.StatusBarManagerInternal;
76 
77 import java.io.FileDescriptor;
78 import java.io.PrintWriter;
79 import java.util.ArrayList;
80 import java.util.Collections;
81 import java.util.List;
82 import java.util.Objects;
83 
84 /**
85  * Provides a mechanism for discovering media routes and manages media playback
86  * behalf of applications.
87  * <p>
88  * Currently supports discovering remote displays via remote display provider
89  * services that have been registered by applications.
90  * </p>
91  */
92 public final class MediaRouterService extends IMediaRouterService.Stub
93         implements Watchdog.Monitor {
94     private static final String TAG = "MediaRouterService";
95     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
96 
97     /**
98      * Timeout in milliseconds for a selected route to transition from a disconnected state to a
99      * connecting state. If we don't observe any progress within this interval, then we will give up
100      * and unselect the route.
101      */
102     private static final long CONNECTING_TIMEOUT = 5000;
103 
104     /**
105      * Timeout in milliseconds for a selected route to transition from a connecting state to a
106      * connected state. If we don't observe any progress within this interval, then we will give up
107      * and unselect the route.
108      */
109     private static final long CONNECTED_TIMEOUT = 60000;
110 
111     private final Context mContext;
112 
113     // State guarded by mLock.
114     private final Object mLock = new Object();
115 
116     private final UserManagerInternal mUserManagerInternal;
117 
118     @GuardedBy("mLock")
119     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
120     @GuardedBy("mLock")
121     private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
122     @GuardedBy("mLock")
123     private int mCurrentActiveUserId = -1;
124 
125     private final IAudioService mAudioService;
126     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
127     private final Handler mHandler = new Handler();
128     private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
129     private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
130 
131     private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver();
132     BluetoothDevice mActiveBluetoothDevice;
133     int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER;
134     boolean mGlobalBluetoothA2dpOn = false;
135 
136     //TODO: remove this when it's finished
137     private final MediaRouter2ServiceImpl mService2;
138     private final String mDefaultAudioRouteId;
139     private final String mBluetoothA2dpRouteId;
140 
141     @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
MediaRouterService(Context context)142     public MediaRouterService(Context context) {
143         mService2 = new MediaRouter2ServiceImpl(context);
144         mContext = context;
145         Watchdog.getInstance().addMonitor(this);
146         Resources res = context.getResources();
147         mDefaultAudioRouteId = res.getString(com.android.internal.R.string.default_audio_route_id);
148         mBluetoothA2dpRouteId =
149                 res.getString(com.android.internal.R.string.bluetooth_a2dp_audio_route_id);
150 
151         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
152         mAudioService = IAudioService.Stub.asInterface(
153                 ServiceManager.getService(Context.AUDIO_SERVICE));
154         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(context);
155         mAudioPlayerStateMonitor.registerListener(
156                 new AudioPlayerActiveStateChangedListenerImpl(), mHandler);
157 
158         try {
159             mAudioService.startWatchingRoutes(new AudioRoutesObserverImpl());
160         } catch (RemoteException e) {
161             Slog.w(TAG, "RemoteException in the audio service.");
162         }
163 
164         IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
165         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
166     }
167 
168     /**
169      * Initializes the MediaRouter service.
170      *
171      * @throws RemoteException If an error occurs while registering the {@link UserSwitchObserver}.
172      */
173     @RequiresPermission(
174             anyOf = {
175                 "android.permission.INTERACT_ACROSS_USERS",
176                 "android.permission.INTERACT_ACROSS_USERS_FULL"
177             })
systemRunning()178     public void systemRunning() throws RemoteException {
179         ActivityManager.getService()
180                 .registerUserSwitchObserver(
181                         new UserSwitchObserver() {
182                             @Override
183                             public void onUserSwitchComplete(int newUserId) {
184                                 updateRunningUserAndProfiles(newUserId);
185                             }
186                         },
187                         TAG);
188         updateRunningUserAndProfiles(ActivityManager.getCurrentUser());
189     }
190 
191     @Override
monitor()192     public void monitor() {
193         synchronized (mLock) { /* check for deadlock */ }
194     }
195 
196     // Binder call
197     @Override
registerClientAsUser(IMediaRouterClient client, String packageName, int userId)198     public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
199         final int uid = Binder.getCallingUid();
200         if (!validatePackageName(uid, packageName)) {
201             throw new SecurityException("packageName must match the calling uid");
202         }
203 
204         final int pid = Binder.getCallingPid();
205         final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
206                 false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
207         final boolean trusted = mContext.checkCallingOrSelfPermission(
208                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
209                 PackageManager.PERMISSION_GRANTED;
210         final long token = Binder.clearCallingIdentity();
211         try {
212             synchronized (mLock) {
213                 registerClientLocked(client, uid, pid, packageName, resolvedUserId, trusted);
214             }
215         } finally {
216             Binder.restoreCallingIdentity(token);
217         }
218     }
219 
220     // Binder call
221     @Override
registerClientGroupId(IMediaRouterClient client, String groupId)222     public void registerClientGroupId(IMediaRouterClient client, String groupId) {
223         if (mContext.checkCallingOrSelfPermission(
224                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
225                 != PackageManager.PERMISSION_GRANTED) {
226             Log.w(TAG, "Ignoring client group request because "
227                     + "the client doesn't have the CONFIGURE_WIFI_DISPLAY permission.");
228             return;
229         }
230         final long token = Binder.clearCallingIdentity();
231         try {
232             synchronized (mLock) {
233                 registerClientGroupIdLocked(client, groupId);
234             }
235         } finally {
236             Binder.restoreCallingIdentity(token);
237         }
238     }
239 
240     // Binder call
241     @Override
unregisterClient(IMediaRouterClient client)242     public void unregisterClient(IMediaRouterClient client) {
243         final long token = Binder.clearCallingIdentity();
244         try {
245             synchronized (mLock) {
246                 unregisterClientLocked(client, false);
247             }
248         } finally {
249             Binder.restoreCallingIdentity(token);
250         }
251     }
252 
253     // Binder call
254     @Override
showMediaOutputSwitcher(String packageName)255     public boolean showMediaOutputSwitcher(String packageName) {
256         if (!validatePackageName(Binder.getCallingUid(), packageName)) {
257             throw new SecurityException("packageName must match the calling identity");
258         }
259         final long token = Binder.clearCallingIdentity();
260         try {
261             if (mContext.getSystemService(ActivityManager.class).getPackageImportance(packageName)
262                     > IMPORTANCE_FOREGROUND) {
263                 Slog.w(TAG, "showMediaOutputSwitcher only works when called from foreground");
264                 return false;
265             }
266             synchronized (mLock) {
267                 StatusBarManagerInternal statusBar =
268                         LocalServices.getService(StatusBarManagerInternal.class);
269                 statusBar.showMediaOutputSwitcher(packageName);
270             }
271         } finally {
272             Binder.restoreCallingIdentity(token);
273         }
274         return true;
275     }
276 
277     // Binder call
278     @Override
getState(IMediaRouterClient client)279     public MediaRouterClientState getState(IMediaRouterClient client) {
280         final long token = Binder.clearCallingIdentity();
281         try {
282             synchronized (mLock) {
283                 return getStateLocked(client);
284             }
285         } finally {
286             Binder.restoreCallingIdentity(token);
287         }
288     }
289 
290     // Binder call
291     @Override
isPlaybackActive(IMediaRouterClient client)292     public boolean isPlaybackActive(IMediaRouterClient client) {
293         final long token = Binder.clearCallingIdentity();
294         try {
295             ClientRecord clientRecord;
296             synchronized (mLock) {
297                 clientRecord = mAllClientRecords.get(client.asBinder());
298             }
299             if (clientRecord != null) {
300                 return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
301             }
302             return false;
303         } finally {
304             Binder.restoreCallingIdentity(token);
305         }
306     }
307 
308     // Binder call
309     @Override
setBluetoothA2dpOn(IMediaRouterClient client, boolean on)310     public void setBluetoothA2dpOn(IMediaRouterClient client, boolean on) {
311         if (client == null) {
312             throw new IllegalArgumentException("client must not be null");
313         }
314 
315         final long token = Binder.clearCallingIdentity();
316         try {
317             mAudioService.setBluetoothA2dpOn(on);
318         } catch (RemoteException ex) {
319             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on);
320         } finally {
321             Binder.restoreCallingIdentity(token);
322         }
323     }
324 
325     // Binder call
326     @Override
setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan)327     public void setDiscoveryRequest(IMediaRouterClient client,
328             int routeTypes, boolean activeScan) {
329         final long token = Binder.clearCallingIdentity();
330         try {
331             synchronized (mLock) {
332                 setDiscoveryRequestLocked(client, routeTypes, activeScan);
333             }
334         } finally {
335             Binder.restoreCallingIdentity(token);
336         }
337     }
338 
339     // Binder call
340     // A null routeId means that the client wants to unselect its current route.
341     // The explicit flag indicates whether the change was explicitly requested by the
342     // user or the application which may cause changes to propagate out to the rest
343     // of the system.  Should be false when the change is in response to a new
344     // selected route or a default selection.
345     @Override
setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit)346     public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
347         final long token = Binder.clearCallingIdentity();
348         try {
349             synchronized (mLock) {
350                 setSelectedRouteLocked(client, routeId, explicit);
351             }
352         } finally {
353             Binder.restoreCallingIdentity(token);
354         }
355     }
356 
357     // Binder call
358     @Override
requestSetVolume(IMediaRouterClient client, String routeId, int volume)359     public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
360         Objects.requireNonNull(routeId, "routeId must not be null");
361 
362         final long token = Binder.clearCallingIdentity();
363         try {
364             synchronized (mLock) {
365                 requestSetVolumeLocked(client, routeId, volume);
366             }
367         } finally {
368             Binder.restoreCallingIdentity(token);
369         }
370     }
371 
372     // Binder call
373     @Override
requestUpdateVolume(IMediaRouterClient client, String routeId, int direction)374     public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
375         Objects.requireNonNull(routeId, "routeId must not be null");
376 
377         final long token = Binder.clearCallingIdentity();
378         try {
379             synchronized (mLock) {
380                 requestUpdateVolumeLocked(client, routeId, direction);
381             }
382         } finally {
383             Binder.restoreCallingIdentity(token);
384         }
385     }
386 
387     // Binder call
388     @Override
dump(FileDescriptor fd, final PrintWriter pw, String[] args)389     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
390         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
391 
392         pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
393         pw.println();
394         pw.println("Global state");
395         pw.println("  mCurrentUserId=" + mCurrentActiveUserId);
396 
397         synchronized (mLock) {
398             final int count = mUserRecords.size();
399             for (int i = 0; i < count; i++) {
400                 UserRecord userRecord = mUserRecords.valueAt(i);
401                 pw.println();
402                 userRecord.dump(pw, "");
403             }
404         }
405 
406         pw.println();
407         mService2.dump(pw, "");
408     }
409 
410     // Binder call
411     @Override
verifyPackageExists(String clientPackageName)412     public boolean verifyPackageExists(String clientPackageName) {
413         return mService2.verifyPackageExists(clientPackageName);
414     }
415 
416     // Binder call
417     @Override
getSystemRoutes()418     public List<MediaRoute2Info> getSystemRoutes() {
419         return mService2.getSystemRoutes();
420     }
421 
422     // Binder call
423     @Override
getSystemSessionInfo()424     public RoutingSessionInfo getSystemSessionInfo() {
425         return mService2.getSystemSessionInfo(
426                 null /* packageName */, false /* setDeviceRouteSelected */);
427     }
428 
429     // Binder call
430     @Override
registerRouter2(IMediaRouter2 router, String packageName)431     public void registerRouter2(IMediaRouter2 router, String packageName) {
432         final int uid = Binder.getCallingUid();
433         if (!validatePackageName(uid, packageName)) {
434             throw new SecurityException("packageName must match the calling uid");
435         }
436         mService2.registerRouter2(router, packageName);
437     }
438 
439     // Binder call
440     @Override
unregisterRouter2(IMediaRouter2 router)441     public void unregisterRouter2(IMediaRouter2 router) {
442         mService2.unregisterRouter2(router);
443     }
444 
445     // Binder call
446     @Override
setDiscoveryRequestWithRouter2(IMediaRouter2 router, RouteDiscoveryPreference request)447     public void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
448             RouteDiscoveryPreference request) {
449         mService2.setDiscoveryRequestWithRouter2(router, request);
450     }
451 
452     // Binder call
453     @Override
setRouteListingPreference( @onNull IMediaRouter2 router, @Nullable RouteListingPreference routeListingPreference)454     public void setRouteListingPreference(
455             @NonNull IMediaRouter2 router,
456             @Nullable RouteListingPreference routeListingPreference) {
457         mService2.setRouteListingPreference(router, routeListingPreference);
458     }
459 
460     // Binder call
461     @Override
setRouteVolumeWithRouter2(IMediaRouter2 router, MediaRoute2Info route, int volume)462     public void setRouteVolumeWithRouter2(IMediaRouter2 router,
463             MediaRoute2Info route, int volume) {
464         mService2.setRouteVolumeWithRouter2(router, route, volume);
465     }
466 
467     // Binder call
468     @Override
requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route, Bundle sessionHints)469     public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
470             long managerRequestId, RoutingSessionInfo oldSession,
471             MediaRoute2Info route, Bundle sessionHints) {
472         mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
473                 oldSession, route, sessionHints);
474     }
475 
476     // Binder call
477     @Override
selectRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)478     public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
479             MediaRoute2Info route) {
480         mService2.selectRouteWithRouter2(router, sessionId, route);
481     }
482 
483     // Binder call
484     @Override
deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)485     public void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId,
486             MediaRoute2Info route) {
487         mService2.deselectRouteWithRouter2(router, sessionId, route);
488     }
489 
490     // Binder call
491     @Override
transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)492     public void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
493             MediaRoute2Info route) {
494         mService2.transferToRouteWithRouter2(router, sessionId, route);
495     }
496 
497     // Binder call
498     @Override
setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume)499     public void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume) {
500         mService2.setSessionVolumeWithRouter2(router, sessionId, volume);
501     }
502 
503     // Binder call
504     @Override
releaseSessionWithRouter2(IMediaRouter2 router, String sessionId)505     public void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId) {
506         mService2.releaseSessionWithRouter2(router, sessionId);
507     }
508 
509     // Binder call
510     @Override
getRemoteSessions(IMediaRouter2Manager manager)511     public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) {
512         return mService2.getRemoteSessions(manager);
513     }
514 
515     // Binder call
516     @Override
getSystemSessionInfoForPackage(IMediaRouter2Manager manager, String packageName)517     public RoutingSessionInfo getSystemSessionInfoForPackage(IMediaRouter2Manager manager,
518             String packageName) {
519         final int uid = Binder.getCallingUid();
520         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
521         boolean setDeviceRouteSelected = false;
522         synchronized (mLock) {
523             UserRecord userRecord = mUserRecords.get(userId);
524             List<ClientRecord> userClientRecords =
525                     userRecord != null ? userRecord.mClientRecords : Collections.emptyList();
526             for (ClientRecord clientRecord : userClientRecords) {
527                 if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
528                     if (mDefaultAudioRouteId.equals(clientRecord.mSelectedRouteId)) {
529                         setDeviceRouteSelected = true;
530                         break;
531                     }
532                 }
533             }
534         }
535         return mService2.getSystemSessionInfo(packageName, setDeviceRouteSelected);
536     }
537 
538     // Binder call
539     @Override
registerManager(IMediaRouter2Manager manager, String packageName)540     public void registerManager(IMediaRouter2Manager manager, String packageName) {
541         final int uid = Binder.getCallingUid();
542         if (!validatePackageName(uid, packageName)) {
543             throw new SecurityException("packageName must match the calling uid");
544         }
545         mService2.registerManager(manager, packageName);
546     }
547 
548     // Binder call
549     @Override
unregisterManager(IMediaRouter2Manager manager)550     public void unregisterManager(IMediaRouter2Manager manager) {
551         mService2.unregisterManager(manager);
552     }
553 
554     // Binder call
555     @Override
startScan(IMediaRouter2Manager manager)556     public void startScan(IMediaRouter2Manager manager) {
557         mService2.startScan(manager);
558     }
559 
560     // Binder call
561     @Override
stopScan(IMediaRouter2Manager manager)562     public void stopScan(IMediaRouter2Manager manager) {
563         mService2.stopScan(manager);
564     }
565 
566     // Binder call
567     @Override
setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, MediaRoute2Info route, int volume)568     public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
569             MediaRoute2Info route, int volume) {
570         mService2.setRouteVolumeWithManager(manager, requestId, route, volume);
571     }
572 
573     // Binder call
574     @Override
requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route)575     public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
576             int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
577         mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
578     }
579 
580     // Binder call
581     @Override
selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)582     public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
583             String sessionId, MediaRoute2Info route) {
584         mService2.selectRouteWithManager(manager, requestId, sessionId, route);
585     }
586 
587     // Binder call
588     @Override
deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)589     public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
590             String sessionId, MediaRoute2Info route) {
591         mService2.deselectRouteWithManager(manager, requestId, sessionId, route);
592     }
593 
594     // Binder call
595     @Override
transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)596     public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
597             String sessionId, MediaRoute2Info route) {
598         mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
599     }
600 
601     // Binder call
602     @Override
setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, int volume)603     public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
604             String sessionId, int volume) {
605         mService2.setSessionVolumeWithManager(manager, requestId, sessionId, volume);
606     }
607 
608     // Binder call
609     @Override
releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId)610     public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId,
611             String sessionId) {
612         mService2.releaseSessionWithManager(manager, requestId, sessionId);
613     }
614 
restoreBluetoothA2dp()615     void restoreBluetoothA2dp() {
616         try {
617             boolean a2dpOn;
618             BluetoothDevice btDevice;
619             synchronized (mLock) {
620                 a2dpOn = mGlobalBluetoothA2dpOn;
621                 btDevice = mActiveBluetoothDevice;
622             }
623             // We don't need to change a2dp status when bluetooth is not connected.
624             if (btDevice != null) {
625                 if (DEBUG) {
626                     Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
627                 }
628                 mAudioService.setBluetoothA2dpOn(a2dpOn);
629             }
630         } catch (RemoteException e) {
631             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
632         }
633     }
634 
restoreRoute(int uid)635     void restoreRoute(int uid) {
636         ClientRecord clientRecord = null;
637         synchronized (mLock) {
638             UserRecord userRecord = mUserRecords.get(
639                     UserHandle.getUserHandleForUid(uid).getIdentifier());
640             if (userRecord != null && userRecord.mClientRecords != null) {
641                 for (ClientRecord cr : userRecord.mClientRecords) {
642                     if (validatePackageName(uid, cr.mPackageName)) {
643                         clientRecord = cr;
644                         break;
645                     }
646                 }
647             }
648         }
649         if (clientRecord != null) {
650             try {
651                 clientRecord.mClient.onRestoreRoute();
652             } catch (RemoteException e) {
653                 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died.");
654             }
655         } else {
656             restoreBluetoothA2dp();
657         }
658     }
659 
660     /**
661      * Starts all {@link UserRecord user records} associated with the active user (whose ID is
662      * {@code newActiveUserId}) or the active user's profiles.
663      *
664      * <p>All other records are stopped, and those without associated client records are removed.
665      */
updateRunningUserAndProfiles(int newActiveUserId)666     private void updateRunningUserAndProfiles(int newActiveUserId) {
667         synchronized (mLock) {
668             if (mCurrentActiveUserId != newActiveUserId) {
669                 mCurrentActiveUserId = newActiveUserId;
670                 // disposeUserIfNeededLocked might modify the collection, hence clone
671                 final var userRecords = mUserRecords.clone();
672                 for (int i = 0; i < userRecords.size(); i++) {
673                     int userId = userRecords.keyAt(i);
674                     UserRecord userRecord = userRecords.valueAt(i);
675                     if (isUserActiveLocked(userId)) {
676                         // userId corresponds to the active user, or one of its profiles. We
677                         // ensure the associated structures are initialized.
678                         userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
679                     } else {
680                         userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
681                         disposeUserIfNeededLocked(userRecord);
682                     }
683                 }
684             }
685         }
686         mService2.updateRunningUserAndProfiles(newActiveUserId);
687     }
688 
clientDied(ClientRecord clientRecord)689     void clientDied(ClientRecord clientRecord) {
690         synchronized (mLock) {
691             unregisterClientLocked(clientRecord.mClient, true);
692         }
693     }
694 
695     @GuardedBy("mLock")
registerClientLocked(IMediaRouterClient client, int uid, int pid, String packageName, int userId, boolean trusted)696     private void registerClientLocked(IMediaRouterClient client,
697             int uid, int pid, String packageName, int userId, boolean trusted) {
698         final IBinder binder = client.asBinder();
699         ClientRecord clientRecord = mAllClientRecords.get(binder);
700         if (clientRecord == null) {
701             boolean newUser = false;
702             UserRecord userRecord = mUserRecords.get(userId);
703             if (userRecord == null) {
704                 userRecord = new UserRecord(userId);
705                 newUser = true;
706             }
707             clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted);
708             try {
709                 binder.linkToDeath(clientRecord, 0);
710             } catch (RemoteException ex) {
711                 throw new RuntimeException("Media router client died prematurely.", ex);
712             }
713 
714             if (newUser) {
715                 mUserRecords.put(userId, userRecord);
716                 initializeUserLocked(userRecord);
717             }
718 
719             userRecord.mClientRecords.add(clientRecord);
720             mAllClientRecords.put(binder, clientRecord);
721             initializeClientLocked(clientRecord);
722         }
723     }
724 
725     @GuardedBy("mLock")
registerClientGroupIdLocked(IMediaRouterClient client, String groupId)726     private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) {
727         final IBinder binder = client.asBinder();
728         ClientRecord clientRecord = mAllClientRecords.get(binder);
729         if (clientRecord == null) {
730             Log.w(TAG, "Ignoring group id register request of a unregistered client.");
731             return;
732         }
733         if (TextUtils.equals(clientRecord.mGroupId, groupId)) {
734             return;
735         }
736         UserRecord userRecord = clientRecord.mUserRecord;
737         if (clientRecord.mGroupId != null) {
738             userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
739         }
740         clientRecord.mGroupId = groupId;
741         if (groupId != null) {
742             userRecord.addToGroup(groupId, clientRecord);
743             userRecord
744                     .mHandler
745                     .obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId)
746                     .sendToTarget();
747         }
748     }
749 
750     @GuardedBy("mLock")
unregisterClientLocked(IMediaRouterClient client, boolean died)751     private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
752         ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
753         if (clientRecord != null) {
754             UserRecord userRecord = clientRecord.mUserRecord;
755             userRecord.mClientRecords.remove(clientRecord);
756             if (clientRecord.mGroupId != null) {
757                 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord);
758                 clientRecord.mGroupId = null;
759             }
760             disposeClientLocked(clientRecord, died);
761             disposeUserIfNeededLocked(userRecord); // since client removed from user
762         }
763     }
764 
765     @GuardedBy("mLock")
getStateLocked(IMediaRouterClient client)766     private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
767         ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
768         if (clientRecord != null) {
769             return clientRecord.getState();
770         }
771         return null;
772     }
773 
774     @GuardedBy("mLock")
setDiscoveryRequestLocked(IMediaRouterClient client, int routeTypes, boolean activeScan)775     private void setDiscoveryRequestLocked(IMediaRouterClient client,
776             int routeTypes, boolean activeScan) {
777         final IBinder binder = client.asBinder();
778         ClientRecord clientRecord = mAllClientRecords.get(binder);
779         if (clientRecord != null) {
780             // Only let the system discover remote display routes for now.
781             if (!clientRecord.mTrusted) {
782                 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
783             }
784 
785             if (clientRecord.mRouteTypes != routeTypes
786                     || clientRecord.mActiveScan != activeScan) {
787                 if (DEBUG) {
788                     Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
789                             + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
790                 }
791                 clientRecord.mRouteTypes = routeTypes;
792                 clientRecord.mActiveScan = activeScan;
793                 clientRecord.mUserRecord.mHandler.sendEmptyMessage(
794                         UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
795             }
796         }
797     }
798 
799     @GuardedBy("mLock")
setSelectedRouteLocked(IMediaRouterClient client, String routeId, boolean explicit)800     private void setSelectedRouteLocked(IMediaRouterClient client,
801             String routeId, boolean explicit) {
802         ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
803         if (clientRecord != null) {
804             // In order not to handle system routes as a global route,
805             // set the IDs null if system routes.
806             final String oldRouteId = (mDefaultAudioRouteId.equals(clientRecord.mSelectedRouteId)
807                     || mBluetoothA2dpRouteId.equals(clientRecord.mSelectedRouteId))
808                     ? null : clientRecord.mSelectedRouteId;
809             clientRecord.mSelectedRouteId = routeId;
810             if (mDefaultAudioRouteId.equals(routeId) || mBluetoothA2dpRouteId.equals(routeId)) {
811                 routeId = null;
812             }
813             if (!Objects.equals(routeId, oldRouteId)) {
814                 if (DEBUG) {
815                     Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
816                             + ", oldRouteId=" + oldRouteId
817                             + ", explicit=" + explicit);
818                 }
819 
820                 // Only let the system connect to new global routes for now.
821                 // A similar check exists in the display manager for wifi display.
822                 if (explicit && clientRecord.mTrusted) {
823                     if (oldRouteId != null) {
824                         clientRecord.mUserRecord.mHandler.obtainMessage(
825                                 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
826                     }
827                     if (routeId != null) {
828                         clientRecord.mUserRecord.mHandler.obtainMessage(
829                                 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
830                     }
831                     if (clientRecord.mGroupId != null) {
832                         ClientGroup group =
833                                 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
834                         if (group != null) {
835                             group.mSelectedRouteId = routeId;
836                             clientRecord
837                                     .mUserRecord
838                                     .mHandler
839                                     .obtainMessage(
840                                             UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED,
841                                             clientRecord.mGroupId)
842                                     .sendToTarget();
843                         }
844                     }
845                 }
846             }
847         }
848     }
849 
850     @GuardedBy("mLock")
requestSetVolumeLocked(IMediaRouterClient client, String routeId, int volume)851     private void requestSetVolumeLocked(IMediaRouterClient client,
852             String routeId, int volume) {
853         final IBinder binder = client.asBinder();
854         ClientRecord clientRecord = mAllClientRecords.get(binder);
855         if (clientRecord != null) {
856             clientRecord.mUserRecord.mHandler.obtainMessage(
857                     UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
858         }
859     }
860 
861     @GuardedBy("mLock")
requestUpdateVolumeLocked(IMediaRouterClient client, String routeId, int direction)862     private void requestUpdateVolumeLocked(IMediaRouterClient client,
863             String routeId, int direction) {
864         final IBinder binder = client.asBinder();
865         ClientRecord clientRecord = mAllClientRecords.get(binder);
866         if (clientRecord != null) {
867             clientRecord.mUserRecord.mHandler.obtainMessage(
868                     UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
869         }
870     }
871 
872     @GuardedBy("mLock")
initializeUserLocked(UserRecord userRecord)873     private void initializeUserLocked(UserRecord userRecord) {
874         if (DEBUG) {
875             Slog.d(TAG, userRecord + ": Initialized");
876         }
877         if (isUserActiveLocked(userRecord.mUserId)) {
878             userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
879         }
880     }
881 
882     @GuardedBy("mLock")
disposeUserIfNeededLocked(UserRecord userRecord)883     private void disposeUserIfNeededLocked(UserRecord userRecord) {
884         // If there are no records left and the user is no longer current then go ahead
885         // and purge the user record and all of its associated state.  If the user is current
886         // then leave it alone since we might be connected to a route or want to query
887         // the same route information again soon.
888         if (!isUserActiveLocked(userRecord.mUserId) && userRecord.mClientRecords.isEmpty()) {
889             if (DEBUG) {
890                 Slog.d(TAG, userRecord + ": Disposed");
891             }
892             mUserRecords.remove(userRecord.mUserId);
893             // Note: User already stopped (by switchUser) so no need to send stop message here.
894         }
895     }
896 
897     /**
898      * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
899      * of the active user, returns {@code false} otherwise.
900      */
901     @GuardedBy("mLock")
isUserActiveLocked(int userId)902     private boolean isUserActiveLocked(int userId) {
903         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
904     }
905 
906     @GuardedBy("mLock")
initializeClientLocked(ClientRecord clientRecord)907     private void initializeClientLocked(ClientRecord clientRecord) {
908         if (DEBUG) {
909             Slog.d(TAG, clientRecord + ": Registered");
910         }
911     }
912 
913     @GuardedBy("mLock")
disposeClientLocked(ClientRecord clientRecord, boolean died)914     private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
915         if (DEBUG) {
916             if (died) {
917                 Slog.d(TAG, clientRecord + ": Died!");
918             } else {
919                 Slog.d(TAG, clientRecord + ": Unregistered");
920             }
921         }
922         if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
923             clientRecord.mUserRecord.mHandler.sendEmptyMessage(
924                     UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
925         }
926         clientRecord.dispose();
927     }
928 
validatePackageName(int uid, String packageName)929     private boolean validatePackageName(int uid, String packageName) {
930         if (packageName != null) {
931             String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
932             if (packageNames != null) {
933                 for (String n : packageNames) {
934                     if (n.equals(packageName)) {
935                         return true;
936                     }
937                 }
938             }
939         }
940         return false;
941     }
942 
943     final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver {
944         @Override
onReceive(Context context, Intent intent)945         public void onReceive(Context context, Intent intent) {
946             if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
947                 BluetoothDevice btDevice =
948                         intent.getParcelableExtra(
949                                 BluetoothDevice.EXTRA_DEVICE,
950                                 android.bluetooth.BluetoothDevice.class);
951                 synchronized (mLock) {
952                     mActiveBluetoothDevice = btDevice;
953                     mGlobalBluetoothA2dpOn = btDevice != null;
954                 }
955             }
956         }
957     }
958 
959     /**
960      * Information about a particular client of the media router.
961      * The contents of this object is guarded by mLock.
962      */
963     final class ClientRecord implements DeathRecipient {
964         public final UserRecord mUserRecord;
965         public final IMediaRouterClient mClient;
966         public final int mUid;
967         public final int mPid;
968         public final String mPackageName;
969         public final boolean mTrusted;
970         public List<String> mControlCategories;
971 
972         public int mRouteTypes;
973         public boolean mActiveScan;
974         public String mSelectedRouteId;
975         public String mGroupId;
976 
ClientRecord(UserRecord userRecord, IMediaRouterClient client, int uid, int pid, String packageName, boolean trusted)977         public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
978                 int uid, int pid, String packageName, boolean trusted) {
979             mUserRecord = userRecord;
980             mClient = client;
981             mUid = uid;
982             mPid = pid;
983             mPackageName = packageName;
984             mTrusted = trusted;
985         }
986 
dispose()987         public void dispose() {
988             mClient.asBinder().unlinkToDeath(this, 0);
989         }
990 
991         @Override
binderDied()992         public void binderDied() {
993             clientDied(this);
994         }
995 
getState()996         MediaRouterClientState getState() {
997             return mTrusted ? mUserRecord.mRouterState : null;
998         }
999 
dump(PrintWriter pw, String prefix)1000         public void dump(PrintWriter pw, String prefix) {
1001             pw.println(prefix + this);
1002 
1003             final String indent = prefix + "  ";
1004             pw.println(indent + "mTrusted=" + mTrusted);
1005             pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
1006             pw.println(indent + "mActiveScan=" + mActiveScan);
1007             pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
1008         }
1009 
1010         @Override
toString()1011         public String toString() {
1012             return "Client " + mPackageName + " (pid " + mPid + ")";
1013         }
1014     }
1015 
1016     final class ClientGroup {
1017         public String mSelectedRouteId;
1018         public final List<ClientRecord> mClientRecords = new ArrayList<>();
1019     }
1020 
1021     /**
1022      * Information about a particular user.
1023      * The contents of this object is guarded by mLock.
1024      */
1025     final class UserRecord {
1026         public final int mUserId;
1027         public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>();
1028         public final UserHandler mHandler;
1029         public MediaRouterClientState mRouterState;
1030         private final ArrayMap<String, ClientGroup> mClientGroupMap = new ArrayMap<>();
1031 
UserRecord(int userId)1032         public UserRecord(int userId) {
1033             mUserId = userId;
1034             mHandler = new UserHandler(MediaRouterService.this, this);
1035         }
1036 
dump(final PrintWriter pw, String prefix)1037         public void dump(final PrintWriter pw, String prefix) {
1038             pw.println(prefix + this);
1039 
1040             final String indent = prefix + "  ";
1041             final int clientCount = mClientRecords.size();
1042             if (clientCount != 0) {
1043                 for (int i = 0; i < clientCount; i++) {
1044                     mClientRecords.get(i).dump(pw, indent);
1045                 }
1046             } else {
1047                 pw.println(indent + "<no clients>");
1048             }
1049 
1050             pw.println(indent + "State");
1051             pw.println(indent + "mRouterState=" + mRouterState);
1052 
1053             if (!mHandler.runWithScissors(new Runnable() {
1054                 @Override
1055                 public void run() {
1056                     mHandler.dump(pw, indent);
1057                 }
1058             }, 1000)) {
1059                 pw.println(indent + "<could not dump handler state>");
1060             }
1061         }
1062 
addToGroup(String groupId, ClientRecord clientRecord)1063         public void addToGroup(String groupId, ClientRecord clientRecord) {
1064             ClientGroup group = mClientGroupMap.get(groupId);
1065             if (group == null) {
1066                 group = new ClientGroup();
1067                 mClientGroupMap.put(groupId, group);
1068             }
1069             group.mClientRecords.add(clientRecord);
1070         }
1071 
removeFromGroup(String groupId, ClientRecord clientRecord)1072         public void removeFromGroup(String groupId, ClientRecord clientRecord) {
1073             ClientGroup group = mClientGroupMap.get(groupId);
1074             if (group != null) {
1075                 group.mClientRecords.remove(clientRecord);
1076                 if (group.mClientRecords.size() == 0) {
1077                     mClientGroupMap.remove(groupId);
1078                 }
1079             }
1080         }
1081 
1082         @Override
toString()1083         public String toString() {
1084             return "User " + mUserId;
1085         }
1086     }
1087 
1088     /**
1089      * Media router handler
1090      * <p>
1091      * Since remote display providers are designed to be single-threaded by nature,
1092      * this class encapsulates all of the associated functionality and exports state
1093      * to the service as it evolves.
1094      * </p><p>
1095      * This class is currently hardcoded to work with remote display providers but
1096      * it is intended to be eventually extended to support more general route providers
1097      * similar to the support library media router.
1098      * </p>
1099      */
1100     static final class UserHandler extends Handler
1101             implements RemoteDisplayProviderWatcher.Callback,
1102             RemoteDisplayProviderProxy.Callback {
1103         public static final int MSG_START = 1;
1104         public static final int MSG_STOP = 2;
1105         public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
1106         public static final int MSG_SELECT_ROUTE = 4;
1107         public static final int MSG_UNSELECT_ROUTE = 5;
1108         public static final int MSG_REQUEST_SET_VOLUME = 6;
1109         public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
1110         private static final int MSG_UPDATE_CLIENT_STATE = 8;
1111         private static final int MSG_CONNECTION_TIMED_OUT = 9;
1112         private static final int MSG_NOTIFY_GROUP_ROUTE_SELECTED = 10;
1113 
1114         private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
1115         private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
1116         private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
1117         private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
1118 
1119         // The relative order of these constants is important and expresses progress
1120         // through the process of connecting to a route.
1121         private static final int PHASE_NOT_AVAILABLE = -1;
1122         private static final int PHASE_NOT_CONNECTED = 0;
1123         private static final int PHASE_CONNECTING = 1;
1124         private static final int PHASE_CONNECTED = 2;
1125 
1126         private final MediaRouterService mService;
1127         private final UserRecord mUserRecord;
1128         private final RemoteDisplayProviderWatcher mWatcher;
1129         private final ArrayList<ProviderRecord> mProviderRecords =
1130                 new ArrayList<ProviderRecord>();
1131         private final ArrayList<IMediaRouterClient> mTempClients =
1132                 new ArrayList<IMediaRouterClient>();
1133 
1134         private boolean mRunning;
1135         private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
1136         private RouteRecord mSelectedRouteRecord;
1137         private int mConnectionPhase = PHASE_NOT_AVAILABLE;
1138         private int mConnectionTimeoutReason;
1139         private long mConnectionTimeoutStartTime;
1140         private boolean mClientStateUpdateScheduled;
1141 
UserHandler(MediaRouterService service, UserRecord userRecord)1142         public UserHandler(MediaRouterService service, UserRecord userRecord) {
1143             super(Looper.getMainLooper(), null, true);
1144             mService = service;
1145             mUserRecord = userRecord;
1146             mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
1147                     this, mUserRecord.mUserId);
1148         }
1149 
1150         @Override
handleMessage(Message msg)1151         public void handleMessage(Message msg) {
1152             switch (msg.what) {
1153                 case MSG_START: {
1154                     start();
1155                     break;
1156                 }
1157                 case MSG_STOP: {
1158                     stop();
1159                     break;
1160                 }
1161                 case MSG_UPDATE_DISCOVERY_REQUEST: {
1162                     updateDiscoveryRequest();
1163                     break;
1164                 }
1165                 case MSG_SELECT_ROUTE: {
1166                     selectRoute((String)msg.obj);
1167                     break;
1168                 }
1169                 case MSG_UNSELECT_ROUTE: {
1170                     unselectRoute((String)msg.obj);
1171                     break;
1172                 }
1173                 case MSG_REQUEST_SET_VOLUME: {
1174                     requestSetVolume((String)msg.obj, msg.arg1);
1175                     break;
1176                 }
1177                 case MSG_REQUEST_UPDATE_VOLUME: {
1178                     requestUpdateVolume((String)msg.obj, msg.arg1);
1179                     break;
1180                 }
1181                 case MSG_UPDATE_CLIENT_STATE: {
1182                     updateClientState();
1183                     break;
1184                 }
1185                 case MSG_CONNECTION_TIMED_OUT: {
1186                     connectionTimedOut();
1187                     break;
1188                 }
1189                 case MSG_NOTIFY_GROUP_ROUTE_SELECTED: {
1190                     notifyGroupRouteSelected((String) msg.obj);
1191                     break;
1192                 }
1193             }
1194         }
1195 
dump(PrintWriter pw, String prefix)1196         public void dump(PrintWriter pw, String prefix) {
1197             pw.println(prefix + "Handler");
1198 
1199             final String indent = prefix + "  ";
1200             pw.println(indent + "mRunning=" + mRunning);
1201             pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
1202             pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord);
1203             pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
1204             pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
1205             pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
1206                     TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
1207 
1208             mWatcher.dump(pw, prefix);
1209 
1210             final int providerCount = mProviderRecords.size();
1211             if (providerCount != 0) {
1212                 for (int i = 0; i < providerCount; i++) {
1213                     mProviderRecords.get(i).dump(pw, prefix);
1214                 }
1215             } else {
1216                 pw.println(indent + "<no providers>");
1217             }
1218         }
1219 
start()1220         private void start() {
1221             if (!mRunning) {
1222                 mRunning = true;
1223                 mWatcher.start(); // also starts all providers
1224             }
1225         }
1226 
stop()1227         private void stop() {
1228             if (mRunning) {
1229                 mRunning = false;
1230                 unselectSelectedRoute();
1231                 mWatcher.stop(); // also stops all providers
1232             }
1233         }
1234 
updateDiscoveryRequest()1235         private void updateDiscoveryRequest() {
1236             int routeTypes = 0;
1237             boolean activeScan = false;
1238             synchronized (mService.mLock) {
1239                 final int count = mUserRecord.mClientRecords.size();
1240                 for (int i = 0; i < count; i++) {
1241                     ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
1242                     routeTypes |= clientRecord.mRouteTypes;
1243                     activeScan |= clientRecord.mActiveScan;
1244                 }
1245             }
1246 
1247             final int newDiscoveryMode;
1248             if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
1249                 if (activeScan) {
1250                     newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
1251                 } else {
1252                     newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
1253                 }
1254             } else {
1255                 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
1256             }
1257 
1258             if (mDiscoveryMode != newDiscoveryMode) {
1259                 mDiscoveryMode = newDiscoveryMode;
1260                 final int count = mProviderRecords.size();
1261                 for (int i = 0; i < count; i++) {
1262                     mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
1263                 }
1264             }
1265         }
1266 
selectRoute(String routeId)1267         private void selectRoute(String routeId) {
1268             if (routeId != null
1269                     && (mSelectedRouteRecord == null
1270                             || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) {
1271                 RouteRecord routeRecord = findRouteRecord(routeId);
1272                 if (routeRecord != null) {
1273                     unselectSelectedRoute();
1274 
1275                     Slog.i(TAG, "Selected route:" + routeRecord);
1276                     mSelectedRouteRecord = routeRecord;
1277                     checkSelectedRouteState();
1278                     routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
1279 
1280                     scheduleUpdateClientState();
1281                 }
1282             }
1283         }
1284 
unselectRoute(String routeId)1285         private void unselectRoute(String routeId) {
1286             if (routeId != null
1287                     && mSelectedRouteRecord != null
1288                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1289                 unselectSelectedRoute();
1290             }
1291         }
1292 
unselectSelectedRoute()1293         private void unselectSelectedRoute() {
1294             if (mSelectedRouteRecord != null) {
1295                 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord);
1296                 mSelectedRouteRecord.getProvider().setSelectedDisplay(null);
1297                 mSelectedRouteRecord = null;
1298                 checkSelectedRouteState();
1299 
1300                 scheduleUpdateClientState();
1301             }
1302         }
1303 
requestSetVolume(String routeId, int volume)1304         private void requestSetVolume(String routeId, int volume) {
1305             if (mSelectedRouteRecord != null
1306                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1307                 mSelectedRouteRecord.getProvider().setDisplayVolume(volume);
1308             }
1309         }
1310 
requestUpdateVolume(String routeId, int direction)1311         private void requestUpdateVolume(String routeId, int direction) {
1312             if (mSelectedRouteRecord != null
1313                     && routeId.equals(mSelectedRouteRecord.getUniqueId())) {
1314                 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
1315             }
1316         }
1317 
1318         @Override
addProvider(RemoteDisplayProviderProxy provider)1319         public void addProvider(RemoteDisplayProviderProxy provider) {
1320             provider.setCallback(this);
1321             provider.setDiscoveryMode(mDiscoveryMode);
1322             provider.setSelectedDisplay(null); // just to be safe
1323 
1324             ProviderRecord providerRecord = new ProviderRecord(provider);
1325             mProviderRecords.add(providerRecord);
1326             providerRecord.updateDescriptor(provider.getDisplayState());
1327 
1328             scheduleUpdateClientState();
1329         }
1330 
1331         @Override
removeProvider(RemoteDisplayProviderProxy provider)1332         public void removeProvider(RemoteDisplayProviderProxy provider) {
1333             int index = findProviderRecord(provider);
1334             if (index >= 0) {
1335                 ProviderRecord providerRecord = mProviderRecords.remove(index);
1336                 providerRecord.updateDescriptor(null); // mark routes invalid
1337                 provider.setCallback(null);
1338                 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
1339 
1340                 checkSelectedRouteState();
1341                 scheduleUpdateClientState();
1342             }
1343         }
1344 
1345         @Override
onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state)1346         public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
1347                 RemoteDisplayState state) {
1348             updateProvider(provider, state);
1349         }
1350 
updateProvider(RemoteDisplayProviderProxy provider, RemoteDisplayState state)1351         private void updateProvider(RemoteDisplayProviderProxy provider,
1352                 RemoteDisplayState state) {
1353             int index = findProviderRecord(provider);
1354             if (index >= 0) {
1355                 ProviderRecord providerRecord = mProviderRecords.get(index);
1356                 if (providerRecord.updateDescriptor(state)) {
1357                     checkSelectedRouteState();
1358                     scheduleUpdateClientState();
1359                 }
1360             }
1361         }
1362 
1363         /**
1364          * This function is called whenever the state of the selected route may have changed.
1365          * It checks the state and updates timeouts or unselects the route as appropriate.
1366          */
checkSelectedRouteState()1367         private void checkSelectedRouteState() {
1368             // Unschedule timeouts when the route is unselected.
1369             if (mSelectedRouteRecord == null) {
1370                 mConnectionPhase = PHASE_NOT_AVAILABLE;
1371                 updateConnectionTimeout(0);
1372                 return;
1373             }
1374 
1375             // Ensure that the route is still present and enabled.
1376             if (!mSelectedRouteRecord.isValid()
1377                     || !mSelectedRouteRecord.isEnabled()) {
1378                 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1379                 return;
1380             }
1381 
1382             // Make sure we haven't lost our connection.
1383             final int oldPhase = mConnectionPhase;
1384             mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus());
1385             if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
1386                 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
1387                 return;
1388             }
1389 
1390             // Check the route status.
1391             switch (mConnectionPhase) {
1392                 case PHASE_CONNECTED:
1393                     if (oldPhase != PHASE_CONNECTED) {
1394                         Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord);
1395                     }
1396                     updateConnectionTimeout(0);
1397                     break;
1398                 case PHASE_CONNECTING:
1399                     if (oldPhase != PHASE_CONNECTING) {
1400                         Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord);
1401                     }
1402                     updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
1403                     break;
1404                 case PHASE_NOT_CONNECTED:
1405                     updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
1406                     break;
1407                 case PHASE_NOT_AVAILABLE:
1408                 default:
1409                     updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
1410                     break;
1411             }
1412         }
1413 
updateConnectionTimeout(int reason)1414         private void updateConnectionTimeout(int reason) {
1415             if (reason != mConnectionTimeoutReason) {
1416                 if (mConnectionTimeoutReason != 0) {
1417                     removeMessages(MSG_CONNECTION_TIMED_OUT);
1418                 }
1419                 mConnectionTimeoutReason = reason;
1420                 mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
1421                 switch (reason) {
1422                     case TIMEOUT_REASON_NOT_AVAILABLE:
1423                     case TIMEOUT_REASON_CONNECTION_LOST:
1424                         // Route became unavailable or connection lost.
1425                         // Unselect it immediately.
1426                         sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
1427                         break;
1428                     case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1429                         // Waiting for route to start connecting.
1430                         sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
1431                         break;
1432                     case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1433                         // Waiting for route to complete connection.
1434                         sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
1435                         break;
1436                 }
1437             }
1438         }
1439 
connectionTimedOut()1440         private void connectionTimedOut() {
1441             if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) {
1442                 // Shouldn't get here.  There must be a bug somewhere.
1443                 Log.wtf(TAG, "Handled connection timeout for no reason.");
1444                 return;
1445             }
1446 
1447             switch (mConnectionTimeoutReason) {
1448                 case TIMEOUT_REASON_NOT_AVAILABLE:
1449                     Slog.i(TAG, "Selected route no longer available: "
1450                             + mSelectedRouteRecord);
1451                     break;
1452                 case TIMEOUT_REASON_CONNECTION_LOST:
1453                     Slog.i(TAG, "Selected route connection lost: "
1454                             + mSelectedRouteRecord);
1455                     break;
1456                 case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
1457                     Slog.i(TAG, "Selected route timed out while waiting for "
1458                             + "connection attempt to begin after "
1459                             + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
1460                             + " ms: " + mSelectedRouteRecord);
1461                     break;
1462                 case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
1463                     Slog.i(TAG, "Selected route timed out while connecting after "
1464                             + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
1465                             + " ms: " + mSelectedRouteRecord);
1466                     break;
1467             }
1468             mConnectionTimeoutReason = 0;
1469 
1470             unselectSelectedRoute();
1471         }
1472 
scheduleUpdateClientState()1473         private void scheduleUpdateClientState() {
1474             if (!mClientStateUpdateScheduled) {
1475                 mClientStateUpdateScheduled = true;
1476                 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
1477             }
1478         }
1479 
updateClientState()1480         private void updateClientState() {
1481             mClientStateUpdateScheduled = false;
1482 
1483             // Build a new client state for trusted clients.
1484             MediaRouterClientState routerState = new MediaRouterClientState();
1485             final int providerCount = mProviderRecords.size();
1486             for (int i = 0; i < providerCount; i++) {
1487                 mProviderRecords.get(i).appendClientState(routerState);
1488             }
1489             try {
1490                 synchronized (mService.mLock) {
1491                     // Update the UserRecord.
1492                     mUserRecord.mRouterState = routerState;
1493 
1494                     // Collect all clients.
1495                     final int count = mUserRecord.mClientRecords.size();
1496                     for (int i = 0; i < count; i++) {
1497                         mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
1498                     }
1499                 }
1500 
1501                 // Notify all clients (outside of the lock).
1502                 final int count = mTempClients.size();
1503                 for (int i = 0; i < count; i++) {
1504                     try {
1505                         mTempClients.get(i).onStateChanged();
1506                     } catch (RemoteException ex) {
1507                         Slog.w(TAG, "Failed to call onStateChanged. Client probably died.");
1508                     }
1509                 }
1510             } finally {
1511                 // Clear the list in preparation for the next time.
1512                 mTempClients.clear();
1513             }
1514         }
1515 
notifyGroupRouteSelected(String groupId)1516         private void notifyGroupRouteSelected(String groupId) {
1517             try {
1518                 String selectedRouteId;
1519                 synchronized (mService.mLock) {
1520                     ClientGroup group = mUserRecord.mClientGroupMap.get(groupId);
1521                     if (group == null) {
1522                         return;
1523                     }
1524                     selectedRouteId = group.mSelectedRouteId;
1525                     final int count = group.mClientRecords.size();
1526                     for (int i = 0; i < count; i++) {
1527                         ClientRecord clientRecord = group.mClientRecords.get(i);
1528                         if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) {
1529                             mTempClients.add(clientRecord.mClient);
1530                         }
1531                     }
1532                 }
1533 
1534                 final int count = mTempClients.size();
1535                 for (int i = 0; i < count; i++) {
1536                     try {
1537                         mTempClients.get(i).onGroupRouteSelected(selectedRouteId);
1538                     } catch (RemoteException ex) {
1539                         Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died.");
1540                     }
1541                 }
1542             } finally {
1543                 mTempClients.clear();
1544             }
1545         }
1546 
findProviderRecord(RemoteDisplayProviderProxy provider)1547         private int findProviderRecord(RemoteDisplayProviderProxy provider) {
1548             final int count = mProviderRecords.size();
1549             for (int i = 0; i < count; i++) {
1550                 ProviderRecord record = mProviderRecords.get(i);
1551                 if (record.getProvider() == provider) {
1552                     return i;
1553                 }
1554             }
1555             return -1;
1556         }
1557 
findRouteRecord(String uniqueId)1558         private RouteRecord findRouteRecord(String uniqueId) {
1559             final int count = mProviderRecords.size();
1560             for (int i = 0; i < count; i++) {
1561                 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
1562                 if (record != null) {
1563                     return record;
1564                 }
1565             }
1566             return null;
1567         }
1568 
getConnectionPhase(int status)1569         private static int getConnectionPhase(int status) {
1570             switch (status) {
1571                 case MediaRouter.RouteInfo.STATUS_NONE:
1572                 case MediaRouter.RouteInfo.STATUS_CONNECTED:
1573                     return PHASE_CONNECTED;
1574                 case MediaRouter.RouteInfo.STATUS_CONNECTING:
1575                     return PHASE_CONNECTING;
1576                 case MediaRouter.RouteInfo.STATUS_SCANNING:
1577                 case MediaRouter.RouteInfo.STATUS_AVAILABLE:
1578                     return PHASE_NOT_CONNECTED;
1579                 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
1580                 case MediaRouter.RouteInfo.STATUS_IN_USE:
1581                 default:
1582                     return PHASE_NOT_AVAILABLE;
1583             }
1584         }
1585 
1586         static final class ProviderRecord {
1587             private final RemoteDisplayProviderProxy mProvider;
1588             private final String mUniquePrefix;
1589             private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
1590             private RemoteDisplayState mDescriptor;
1591 
ProviderRecord(RemoteDisplayProviderProxy provider)1592             public ProviderRecord(RemoteDisplayProviderProxy provider) {
1593                 mProvider = provider;
1594                 mUniquePrefix = provider.getFlattenedComponentName() + ":";
1595             }
1596 
getProvider()1597             public RemoteDisplayProviderProxy getProvider() {
1598                 return mProvider;
1599             }
1600 
getUniquePrefix()1601             public String getUniquePrefix() {
1602                 return mUniquePrefix;
1603             }
1604 
updateDescriptor(RemoteDisplayState descriptor)1605             public boolean updateDescriptor(RemoteDisplayState descriptor) {
1606                 boolean changed = false;
1607                 if (mDescriptor != descriptor) {
1608                     mDescriptor = descriptor;
1609 
1610                     // Update all existing routes and reorder them to match
1611                     // the order of their descriptors.
1612                     int targetIndex = 0;
1613                     if (descriptor != null) {
1614                         if (descriptor.isValid()) {
1615                             final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
1616                             final int routeCount = routeDescriptors.size();
1617                             for (int i = 0; i < routeCount; i++) {
1618                                 final RemoteDisplayInfo routeDescriptor =
1619                                         routeDescriptors.get(i);
1620                                 final String descriptorId = routeDescriptor.id;
1621                                 final int sourceIndex = findRouteByDescriptorId(descriptorId);
1622                                 if (sourceIndex < 0) {
1623                                     // Add the route to the provider.
1624                                     String uniqueId = assignRouteUniqueId(descriptorId);
1625                                     RouteRecord route =
1626                                             new RouteRecord(this, descriptorId, uniqueId);
1627                                     mRoutes.add(targetIndex++, route);
1628                                     route.updateDescriptor(routeDescriptor);
1629                                     changed = true;
1630                                 } else if (sourceIndex < targetIndex) {
1631                                     // Ignore route with duplicate id.
1632                                     Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
1633                                             + routeDescriptor);
1634                                 } else {
1635                                     // Reorder existing route within the list.
1636                                     RouteRecord route = mRoutes.get(sourceIndex);
1637                                     Collections.swap(mRoutes, sourceIndex, targetIndex++);
1638                                     changed |= route.updateDescriptor(routeDescriptor);
1639                                 }
1640                             }
1641                         } else {
1642                             Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
1643                                     + mProvider.getFlattenedComponentName());
1644                         }
1645                     }
1646 
1647                     // Dispose all remaining routes that do not have matching descriptors.
1648                     for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
1649                         RouteRecord route = mRoutes.remove(i);
1650                         route.updateDescriptor(null); // mark route invalid
1651                         changed = true;
1652                     }
1653                 }
1654                 return changed;
1655             }
1656 
appendClientState(MediaRouterClientState state)1657             public void appendClientState(MediaRouterClientState state) {
1658                 final int routeCount = mRoutes.size();
1659                 for (int i = 0; i < routeCount; i++) {
1660                     state.routes.add(mRoutes.get(i).getInfo());
1661                 }
1662             }
1663 
findRouteByUniqueId(String uniqueId)1664             public RouteRecord findRouteByUniqueId(String uniqueId) {
1665                 final int routeCount = mRoutes.size();
1666                 for (int i = 0; i < routeCount; i++) {
1667                     RouteRecord route = mRoutes.get(i);
1668                     if (route.getUniqueId().equals(uniqueId)) {
1669                         return route;
1670                     }
1671                 }
1672                 return null;
1673             }
1674 
findRouteByDescriptorId(String descriptorId)1675             private int findRouteByDescriptorId(String descriptorId) {
1676                 final int routeCount = mRoutes.size();
1677                 for (int i = 0; i < routeCount; i++) {
1678                     RouteRecord route = mRoutes.get(i);
1679                     if (route.getDescriptorId().equals(descriptorId)) {
1680                         return i;
1681                     }
1682                 }
1683                 return -1;
1684             }
1685 
dump(PrintWriter pw, String prefix)1686             public void dump(PrintWriter pw, String prefix) {
1687                 pw.println(prefix + this);
1688 
1689                 final String indent = prefix + "  ";
1690                 mProvider.dump(pw, indent);
1691 
1692                 final int routeCount = mRoutes.size();
1693                 if (routeCount != 0) {
1694                     for (int i = 0; i < routeCount; i++) {
1695                         mRoutes.get(i).dump(pw, indent);
1696                     }
1697                 } else {
1698                     pw.println(indent + "<no routes>");
1699                 }
1700             }
1701 
1702             @Override
toString()1703             public String toString() {
1704                 return "Provider " + mProvider.getFlattenedComponentName();
1705             }
1706 
assignRouteUniqueId(String descriptorId)1707             private String assignRouteUniqueId(String descriptorId) {
1708                 return mUniquePrefix + descriptorId;
1709             }
1710         }
1711 
1712         static final class RouteRecord {
1713             private final ProviderRecord mProviderRecord;
1714             private final String mDescriptorId;
1715             private final MediaRouterClientState.RouteInfo mMutableInfo;
1716             private MediaRouterClientState.RouteInfo mImmutableInfo;
1717             private RemoteDisplayInfo mDescriptor;
1718 
RouteRecord(ProviderRecord providerRecord, String descriptorId, String uniqueId)1719             public RouteRecord(ProviderRecord providerRecord,
1720                     String descriptorId, String uniqueId) {
1721                 mProviderRecord = providerRecord;
1722                 mDescriptorId = descriptorId;
1723                 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
1724             }
1725 
getProvider()1726             public RemoteDisplayProviderProxy getProvider() {
1727                 return mProviderRecord.getProvider();
1728             }
1729 
getProviderRecord()1730             public ProviderRecord getProviderRecord() {
1731                 return mProviderRecord;
1732             }
1733 
getDescriptorId()1734             public String getDescriptorId() {
1735                 return mDescriptorId;
1736             }
1737 
getUniqueId()1738             public String getUniqueId() {
1739                 return mMutableInfo.id;
1740             }
1741 
getInfo()1742             public MediaRouterClientState.RouteInfo getInfo() {
1743                 if (mImmutableInfo == null) {
1744                     mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
1745                 }
1746                 return mImmutableInfo;
1747             }
1748 
isValid()1749             public boolean isValid() {
1750                 return mDescriptor != null;
1751             }
1752 
isEnabled()1753             public boolean isEnabled() {
1754                 return mMutableInfo.enabled;
1755             }
1756 
getStatus()1757             public int getStatus() {
1758                 return mMutableInfo.statusCode;
1759             }
1760 
updateDescriptor(RemoteDisplayInfo descriptor)1761             public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
1762                 boolean changed = false;
1763                 if (mDescriptor != descriptor) {
1764                     mDescriptor = descriptor;
1765                     if (descriptor != null) {
1766                         final String name = computeName(descriptor);
1767                         if (!Objects.equals(mMutableInfo.name, name)) {
1768                             mMutableInfo.name = name;
1769                             changed = true;
1770                         }
1771                         final String description = computeDescription(descriptor);
1772                         if (!Objects.equals(mMutableInfo.description, description)) {
1773                             mMutableInfo.description = description;
1774                             changed = true;
1775                         }
1776                         final int supportedTypes = computeSupportedTypes(descriptor);
1777                         if (mMutableInfo.supportedTypes != supportedTypes) {
1778                             mMutableInfo.supportedTypes = supportedTypes;
1779                             changed = true;
1780                         }
1781                         final boolean enabled = computeEnabled(descriptor);
1782                         if (mMutableInfo.enabled != enabled) {
1783                             mMutableInfo.enabled = enabled;
1784                             changed = true;
1785                         }
1786                         final int statusCode = computeStatusCode(descriptor);
1787                         if (mMutableInfo.statusCode != statusCode) {
1788                             mMutableInfo.statusCode = statusCode;
1789                             changed = true;
1790                         }
1791                         final int playbackType = computePlaybackType(descriptor);
1792                         if (mMutableInfo.playbackType != playbackType) {
1793                             mMutableInfo.playbackType = playbackType;
1794                             changed = true;
1795                         }
1796                         final int playbackStream = computePlaybackStream(descriptor);
1797                         if (mMutableInfo.playbackStream != playbackStream) {
1798                             mMutableInfo.playbackStream = playbackStream;
1799                             changed = true;
1800                         }
1801                         final int volume = computeVolume(descriptor);
1802                         if (mMutableInfo.volume != volume) {
1803                             mMutableInfo.volume = volume;
1804                             changed = true;
1805                         }
1806                         final int volumeMax = computeVolumeMax(descriptor);
1807                         if (mMutableInfo.volumeMax != volumeMax) {
1808                             mMutableInfo.volumeMax = volumeMax;
1809                             changed = true;
1810                         }
1811                         final int volumeHandling = computeVolumeHandling(descriptor);
1812                         if (mMutableInfo.volumeHandling != volumeHandling) {
1813                             mMutableInfo.volumeHandling = volumeHandling;
1814                             changed = true;
1815                         }
1816                         final int presentationDisplayId = computePresentationDisplayId(descriptor);
1817                         if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
1818                             mMutableInfo.presentationDisplayId = presentationDisplayId;
1819                             changed = true;
1820                         }
1821                     }
1822                 }
1823                 if (changed) {
1824                     mImmutableInfo = null;
1825                 }
1826                 return changed;
1827             }
1828 
dump(PrintWriter pw, String prefix)1829             public void dump(PrintWriter pw, String prefix) {
1830                 pw.println(prefix + this);
1831 
1832                 final String indent = prefix + "  ";
1833                 pw.println(indent + "mMutableInfo=" + mMutableInfo);
1834                 pw.println(indent + "mDescriptorId=" + mDescriptorId);
1835                 pw.println(indent + "mDescriptor=" + mDescriptor);
1836             }
1837 
1838             @Override
toString()1839             public String toString() {
1840                 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
1841             }
1842 
computeName(RemoteDisplayInfo descriptor)1843             private static String computeName(RemoteDisplayInfo descriptor) {
1844                 // Note that isValid() already ensures the name is non-empty.
1845                 return descriptor.name;
1846             }
1847 
computeDescription(RemoteDisplayInfo descriptor)1848             private static String computeDescription(RemoteDisplayInfo descriptor) {
1849                 final String description = descriptor.description;
1850                 return TextUtils.isEmpty(description) ? null : description;
1851             }
1852 
computeSupportedTypes(RemoteDisplayInfo descriptor)1853             private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
1854                 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
1855                         | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
1856                         | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
1857             }
1858 
computeEnabled(RemoteDisplayInfo descriptor)1859             private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
1860                 switch (descriptor.status) {
1861                     case RemoteDisplayInfo.STATUS_CONNECTED:
1862                     case RemoteDisplayInfo.STATUS_CONNECTING:
1863                     case RemoteDisplayInfo.STATUS_AVAILABLE:
1864                         return true;
1865                     default:
1866                         return false;
1867                 }
1868             }
1869 
computeStatusCode(RemoteDisplayInfo descriptor)1870             private static int computeStatusCode(RemoteDisplayInfo descriptor) {
1871                 switch (descriptor.status) {
1872                     case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
1873                         return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
1874                     case RemoteDisplayInfo.STATUS_AVAILABLE:
1875                         return MediaRouter.RouteInfo.STATUS_AVAILABLE;
1876                     case RemoteDisplayInfo.STATUS_IN_USE:
1877                         return MediaRouter.RouteInfo.STATUS_IN_USE;
1878                     case RemoteDisplayInfo.STATUS_CONNECTING:
1879                         return MediaRouter.RouteInfo.STATUS_CONNECTING;
1880                     case RemoteDisplayInfo.STATUS_CONNECTED:
1881                         return MediaRouter.RouteInfo.STATUS_CONNECTED;
1882                     default:
1883                         return MediaRouter.RouteInfo.STATUS_NONE;
1884                 }
1885             }
1886 
computePlaybackType(RemoteDisplayInfo descriptor)1887             private static int computePlaybackType(RemoteDisplayInfo descriptor) {
1888                 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
1889             }
1890 
computePlaybackStream(RemoteDisplayInfo descriptor)1891             private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
1892                 return AudioSystem.STREAM_MUSIC;
1893             }
1894 
computeVolume(RemoteDisplayInfo descriptor)1895             private static int computeVolume(RemoteDisplayInfo descriptor) {
1896                 final int volume = descriptor.volume;
1897                 final int volumeMax = descriptor.volumeMax;
1898                 if (volume < 0) {
1899                     return 0;
1900                 } else if (volume > volumeMax) {
1901                     return volumeMax;
1902                 }
1903                 return volume;
1904             }
1905 
computeVolumeMax(RemoteDisplayInfo descriptor)1906             private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
1907                 final int volumeMax = descriptor.volumeMax;
1908                 return volumeMax > 0 ? volumeMax : 0;
1909             }
1910 
computeVolumeHandling(RemoteDisplayInfo descriptor)1911             private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
1912                 final int volumeHandling = descriptor.volumeHandling;
1913                 switch (volumeHandling) {
1914                     case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
1915                         return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1916                     case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
1917                     default:
1918                         return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
1919                 }
1920             }
1921 
computePresentationDisplayId(RemoteDisplayInfo descriptor)1922             private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
1923                 // The MediaRouter class validates that the id corresponds to an extant
1924                 // presentation display.  So all we do here is canonicalize the null case.
1925                 final int displayId = descriptor.presentationDisplayId;
1926                 return displayId < 0 ? -1 : displayId;
1927             }
1928         }
1929     }
1930 
1931     private class AudioPlayerActiveStateChangedListenerImpl
1932             implements AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener {
1933 
1934         private static final long WAIT_MS = 500;
1935         private final Runnable mRestoreBluetoothA2dpRunnable =
1936                 MediaRouterService.this::restoreBluetoothA2dp;
1937 
1938         @Override
onAudioPlayerActiveStateChanged( @onNull AudioPlaybackConfiguration config, boolean isRemoved)1939         public void onAudioPlayerActiveStateChanged(
1940                 @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
1941             boolean active = !isRemoved && config.isActive();
1942             int uid = config.getClientUid();
1943 
1944             int idx = mActivePlayerMinPriorityQueue.indexOf(config.getPlayerInterfaceId());
1945             // Keep the latest active player and its uid at the end of the queue.
1946             if (idx >= 0) {
1947                 mActivePlayerMinPriorityQueue.remove(idx);
1948                 mActivePlayerUidMinPriorityQueue.remove(idx);
1949             }
1950 
1951             int restoreUid = -1;
1952             if (active) {
1953                 mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
1954                 mActivePlayerUidMinPriorityQueue.add(uid);
1955                 restoreUid = uid;
1956             } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
1957                 restoreUid =
1958                         mActivePlayerUidMinPriorityQueue.get(
1959                                 mActivePlayerUidMinPriorityQueue.size() - 1);
1960             }
1961 
1962             mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
1963             if (restoreUid >= 0) {
1964                 restoreRoute(restoreUid);
1965                 if (DEBUG) {
1966                     Slog.d(
1967                             TAG,
1968                             "onAudioPlayerActiveStateChanged: "
1969                                     + "uid="
1970                                     + uid
1971                                     + ", active="
1972                                     + active
1973                                     + ", restoreUid="
1974                                     + restoreUid);
1975                 }
1976             } else {
1977                 mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
1978                 if (DEBUG) {
1979                     Slog.d(
1980                             TAG,
1981                             "onAudioPlayerActiveStateChanged: "
1982                                     + "uid="
1983                                     + uid
1984                                     + ", active="
1985                                     + active
1986                                     + ", delaying");
1987                 }
1988             }
1989         }
1990     }
1991 
1992     private class AudioRoutesObserverImpl extends IAudioRoutesObserver.Stub {
1993 
1994         private static final int HEADSET_FLAGS =
1995                 AudioRoutesInfo.MAIN_HEADSET
1996                         | AudioRoutesInfo.MAIN_HEADPHONES
1997                         | AudioRoutesInfo.MAIN_USB;
1998 
1999         @Override
dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes)2000         public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
2001             synchronized (mLock) {
2002                 if (newRoutes.mainType != mAudioRouteMainType) {
2003                     if ((newRoutes.mainType & HEADSET_FLAGS) == 0) {
2004                         // headset was plugged out.
2005                         mGlobalBluetoothA2dpOn =
2006                                 newRoutes.bluetoothName != null || mActiveBluetoothDevice != null;
2007                     } else {
2008                         // headset was plugged in.
2009                         mGlobalBluetoothA2dpOn = false;
2010                     }
2011                     mAudioRouteMainType = newRoutes.mainType;
2012                 }
2013                 // The new audio routes info could be delivered with several seconds delay.
2014                 // In order to avoid such delay, Bluetooth device info will be updated
2015                 // via MediaRouterServiceBroadcastReceiver.
2016             }
2017         }
2018     }
2019 }
2020