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