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