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