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