• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.media;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
20 import static android.content.Intent.ACTION_SCREEN_OFF;
21 import static android.content.Intent.ACTION_SCREEN_ON;
22 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
23 import static android.media.MediaRouter2Utils.getOriginalId;
24 import static android.media.MediaRouter2Utils.getProviderId;
25 
26 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
27 
28 import android.Manifest;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.app.ActivityManager;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.PackageManager;
37 import android.media.IMediaRouter2;
38 import android.media.IMediaRouter2Manager;
39 import android.media.MediaRoute2Info;
40 import android.media.MediaRoute2ProviderInfo;
41 import android.media.MediaRoute2ProviderService;
42 import android.media.MediaRouter2Manager;
43 import android.media.RouteDiscoveryPreference;
44 import android.media.RoutingSessionInfo;
45 import android.os.Binder;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IBinder;
49 import android.os.Looper;
50 import android.os.PowerManager;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.text.TextUtils;
54 import android.util.ArrayMap;
55 import android.util.Log;
56 import android.util.Slog;
57 import android.util.SparseArray;
58 
59 import com.android.internal.annotations.GuardedBy;
60 import com.android.internal.util.function.pooled.PooledLambda;
61 import com.android.server.LocalServices;
62 import com.android.server.pm.UserManagerInternal;
63 
64 import java.io.PrintWriter;
65 import java.lang.ref.WeakReference;
66 import java.util.ArrayList;
67 import java.util.Collection;
68 import java.util.Collections;
69 import java.util.HashSet;
70 import java.util.List;
71 import java.util.Map;
72 import java.util.Objects;
73 import java.util.Set;
74 import java.util.concurrent.CopyOnWriteArrayList;
75 import java.util.concurrent.atomic.AtomicInteger;
76 import java.util.stream.Collectors;
77 
78 /**
79  * Implements features related to {@link android.media.MediaRouter2} and
80  * {@link android.media.MediaRouter2Manager}.
81  */
82 class MediaRouter2ServiceImpl {
83     private static final String TAG = "MR2ServiceImpl";
84     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
85 
86     // TODO: (In Android S or later) if we add callback methods for generic failures
87     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
88     private static final long DUMMY_REQUEST_ID = -1;
89     private static final int PACKAGE_IMPORTANCE_FOR_DISCOVERY = IMPORTANCE_FOREGROUND_SERVICE;
90 
91     private final Context mContext;
92     private final UserManagerInternal mUserManagerInternal;
93     private final Object mLock = new Object();
94     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
95     final ActivityManager mActivityManager;
96     final PowerManager mPowerManager;
97 
98     @GuardedBy("mLock")
99     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
100     @GuardedBy("mLock")
101     private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
102     @GuardedBy("mLock")
103     private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
104     @GuardedBy("mLock")
105     private int mCurrentActiveUserId = -1;
106 
107     private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
108             (uid, importance) -> {
109                 synchronized (mLock) {
110                     final int count = mUserRecords.size();
111                     for (int i = 0; i < count; i++) {
112                         mUserRecords.valueAt(i).mHandler.maybeUpdateDiscoveryPreferenceForUid(uid);
113                     }
114                 }
115             };
116 
117     private final BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() {
118         @Override
119         public void onReceive(Context context, Intent intent) {
120             synchronized (mLock) {
121                 final int count = mUserRecords.size();
122                 for (int i = 0; i < count; i++) {
123                     UserHandler userHandler = mUserRecords.valueAt(i).mHandler;
124                     userHandler.sendMessage(PooledLambda.obtainMessage(
125                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
126                 }
127             }
128         }
129     };
130 
MediaRouter2ServiceImpl(Context context)131     /* package */ MediaRouter2ServiceImpl(Context context) {
132         mContext = context;
133         mActivityManager = mContext.getSystemService(ActivityManager.class);
134         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
135                 PACKAGE_IMPORTANCE_FOR_DISCOVERY);
136         mPowerManager = mContext.getSystemService(PowerManager.class);
137         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
138 
139         IntentFilter screenOnOffIntentFilter = new IntentFilter();
140         screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
141         screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
142 
143         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
144     }
145 
146     ////////////////////////////////////////////////////////////////
147     ////  Calls from MediaRouter2
148     ////   - Should not have @NonNull/@Nullable on any arguments
149     ////////////////////////////////////////////////////////////////
150 
151     @NonNull
enforceMediaContentControlPermission()152     public void enforceMediaContentControlPermission() {
153         final int pid = Binder.getCallingPid();
154         final int uid = Binder.getCallingUid();
155         final long token = Binder.clearCallingIdentity();
156 
157         try {
158             mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
159                     "Must hold MEDIA_CONTENT_CONTROL permission.");
160         } finally {
161             Binder.restoreCallingIdentity(token);
162         }
163     }
164 
165     @NonNull
getSystemRoutes()166     public List<MediaRoute2Info> getSystemRoutes() {
167         final int uid = Binder.getCallingUid();
168         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
169         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
170                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
171                 == PackageManager.PERMISSION_GRANTED;
172 
173         final long token = Binder.clearCallingIdentity();
174         try {
175             Collection<MediaRoute2Info> systemRoutes;
176             synchronized (mLock) {
177                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
178                 if (hasModifyAudioRoutingPermission) {
179                     MediaRoute2ProviderInfo providerInfo =
180                             userRecord.mHandler.mSystemProvider.getProviderInfo();
181                     if (providerInfo != null) {
182                         systemRoutes = providerInfo.getRoutes();
183                     } else {
184                         systemRoutes = Collections.emptyList();
185                     }
186                 } else {
187                     systemRoutes = new ArrayList<>();
188                     systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
189                 }
190             }
191             return new ArrayList<>(systemRoutes);
192         } finally {
193             Binder.restoreCallingIdentity(token);
194         }
195     }
196 
197     @NonNull
getSystemSessionInfo( @ullable String packageName, boolean setDeviceRouteSelected)198     public RoutingSessionInfo getSystemSessionInfo(
199             @Nullable String packageName, boolean setDeviceRouteSelected) {
200         final int uid = Binder.getCallingUid();
201         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
202         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
203                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
204                 == PackageManager.PERMISSION_GRANTED;
205 
206         final long token = Binder.clearCallingIdentity();
207         try {
208             RoutingSessionInfo systemSessionInfo = null;
209             synchronized (mLock) {
210                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
211                 List<RoutingSessionInfo> sessionInfos;
212                 if (hasModifyAudioRoutingPermission) {
213                     if (setDeviceRouteSelected) {
214                         systemSessionInfo = userRecord.mHandler.mSystemProvider
215                                 .generateDeviceRouteSelectedSessionInfo(packageName);
216                     } else {
217                         sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
218                         if (sessionInfos != null && !sessionInfos.isEmpty()) {
219                             systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0))
220                                     .setClientPackageName(packageName).build();
221                         } else {
222                             Slog.w(TAG, "System provider does not have any session info.");
223                         }
224                     }
225                 } else {
226                     systemSessionInfo = new RoutingSessionInfo.Builder(
227                             userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
228                             .setClientPackageName(packageName).build();
229                 }
230             }
231             return systemSessionInfo;
232         } finally {
233             Binder.restoreCallingIdentity(token);
234         }
235     }
236 
registerRouter2(IMediaRouter2 router, String packageName)237     public void registerRouter2(IMediaRouter2 router, String packageName) {
238         Objects.requireNonNull(router, "router must not be null");
239         if (TextUtils.isEmpty(packageName)) {
240             throw new IllegalArgumentException("packageName must not be empty");
241         }
242 
243         final int uid = Binder.getCallingUid();
244         final int pid = Binder.getCallingPid();
245         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
246         final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
247                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
248                 == PackageManager.PERMISSION_GRANTED;
249         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
250                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
251                 == PackageManager.PERMISSION_GRANTED;
252 
253         final long token = Binder.clearCallingIdentity();
254         try {
255             synchronized (mLock) {
256                 registerRouter2Locked(router, uid, pid, packageName, userId,
257                         hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
258             }
259         } finally {
260             Binder.restoreCallingIdentity(token);
261         }
262     }
263 
unregisterRouter2(IMediaRouter2 router)264     public void unregisterRouter2(IMediaRouter2 router) {
265         Objects.requireNonNull(router, "router must not be null");
266 
267         final long token = Binder.clearCallingIdentity();
268         try {
269             synchronized (mLock) {
270                 unregisterRouter2Locked(router, false);
271             }
272         } finally {
273             Binder.restoreCallingIdentity(token);
274         }
275     }
276 
setDiscoveryRequestWithRouter2(IMediaRouter2 router, RouteDiscoveryPreference preference)277     public void setDiscoveryRequestWithRouter2(IMediaRouter2 router,
278             RouteDiscoveryPreference preference) {
279         Objects.requireNonNull(router, "router must not be null");
280         Objects.requireNonNull(preference, "preference must not be null");
281 
282         final long token = Binder.clearCallingIdentity();
283         try {
284             synchronized (mLock) {
285                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
286                 if (routerRecord == null) {
287                     Slog.w(TAG, "Ignoring updating discoveryRequest of null routerRecord.");
288                     return;
289                 }
290                 setDiscoveryRequestWithRouter2Locked(routerRecord, preference);
291             }
292         } finally {
293             Binder.restoreCallingIdentity(token);
294         }
295     }
296 
setRouteVolumeWithRouter2(IMediaRouter2 router, MediaRoute2Info route, int volume)297     public void setRouteVolumeWithRouter2(IMediaRouter2 router,
298             MediaRoute2Info route, int volume) {
299         Objects.requireNonNull(router, "router must not be null");
300         Objects.requireNonNull(route, "route must not be null");
301 
302         final long token = Binder.clearCallingIdentity();
303         try {
304             synchronized (mLock) {
305                 setRouteVolumeWithRouter2Locked(router, route, volume);
306             }
307         } finally {
308             Binder.restoreCallingIdentity(token);
309         }
310     }
311 
requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route, Bundle sessionHints)312     public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
313             long managerRequestId, RoutingSessionInfo oldSession,
314             MediaRoute2Info route, Bundle sessionHints) {
315         Objects.requireNonNull(router, "router must not be null");
316         Objects.requireNonNull(oldSession, "oldSession must not be null");
317         Objects.requireNonNull(route, "route must not be null");
318 
319         final long token = Binder.clearCallingIdentity();
320         try {
321             synchronized (mLock) {
322                 requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
323                         router, oldSession, route, sessionHints);
324             }
325         } finally {
326             Binder.restoreCallingIdentity(token);
327         }
328     }
329 
selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, MediaRoute2Info route)330     public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
331             MediaRoute2Info route) {
332         Objects.requireNonNull(router, "router must not be null");
333         Objects.requireNonNull(route, "route must not be null");
334         if (TextUtils.isEmpty(uniqueSessionId)) {
335             throw new IllegalArgumentException("uniqueSessionId must not be empty");
336         }
337 
338         final long token = Binder.clearCallingIdentity();
339         try {
340             synchronized (mLock) {
341                 selectRouteWithRouter2Locked(router, uniqueSessionId, route);
342             }
343         } finally {
344             Binder.restoreCallingIdentity(token);
345         }
346     }
347 
deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, MediaRoute2Info route)348     public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
349             MediaRoute2Info route) {
350         Objects.requireNonNull(router, "router must not be null");
351         Objects.requireNonNull(route, "route must not be null");
352         if (TextUtils.isEmpty(uniqueSessionId)) {
353             throw new IllegalArgumentException("uniqueSessionId must not be empty");
354         }
355 
356         final long token = Binder.clearCallingIdentity();
357         try {
358             synchronized (mLock) {
359                 deselectRouteWithRouter2Locked(router, uniqueSessionId, route);
360             }
361         } finally {
362             Binder.restoreCallingIdentity(token);
363         }
364     }
365 
transferToRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, MediaRoute2Info route)366     public void transferToRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
367             MediaRoute2Info route) {
368         Objects.requireNonNull(router, "router must not be null");
369         Objects.requireNonNull(route, "route must not be null");
370         if (TextUtils.isEmpty(uniqueSessionId)) {
371             throw new IllegalArgumentException("uniqueSessionId must not be empty");
372         }
373 
374         final long token = Binder.clearCallingIdentity();
375         try {
376             synchronized (mLock) {
377                 transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
378             }
379         } finally {
380             Binder.restoreCallingIdentity(token);
381         }
382     }
383 
setSessionVolumeWithRouter2(IMediaRouter2 router, String uniqueSessionId, int volume)384     public void setSessionVolumeWithRouter2(IMediaRouter2 router, String uniqueSessionId,
385             int volume) {
386         Objects.requireNonNull(router, "router must not be null");
387         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
388 
389         final long token = Binder.clearCallingIdentity();
390         try {
391             synchronized (mLock) {
392                 setSessionVolumeWithRouter2Locked(router, uniqueSessionId, volume);
393             }
394         } finally {
395             Binder.restoreCallingIdentity(token);
396         }
397     }
398 
releaseSessionWithRouter2(IMediaRouter2 router, String uniqueSessionId)399     public void releaseSessionWithRouter2(IMediaRouter2 router, String uniqueSessionId) {
400         Objects.requireNonNull(router, "router must not be null");
401         if (TextUtils.isEmpty(uniqueSessionId)) {
402             throw new IllegalArgumentException("uniqueSessionId must not be empty");
403         }
404 
405         final long token = Binder.clearCallingIdentity();
406         try {
407             synchronized (mLock) {
408                 releaseSessionWithRouter2Locked(router, uniqueSessionId);
409             }
410         } finally {
411             Binder.restoreCallingIdentity(token);
412         }
413     }
414 
415     ////////////////////////////////////////////////////////////////
416     ////  Calls from MediaRouter2Manager
417     ////   - Should not have @NonNull/@Nullable on any arguments
418     ////////////////////////////////////////////////////////////////
419 
420     @NonNull
getRemoteSessions(IMediaRouter2Manager manager)421     public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) {
422         Objects.requireNonNull(manager, "manager must not be null");
423         final long token = Binder.clearCallingIdentity();
424         try {
425             synchronized (mLock) {
426                 return getRemoteSessionsLocked(manager);
427             }
428         } finally {
429             Binder.restoreCallingIdentity(token);
430         }
431     }
432 
registerManager(IMediaRouter2Manager manager, String packageName)433     public void registerManager(IMediaRouter2Manager manager, String packageName) {
434         Objects.requireNonNull(manager, "manager must not be null");
435         if (TextUtils.isEmpty(packageName)) {
436             throw new IllegalArgumentException("packageName must not be empty");
437         }
438 
439         final int uid = Binder.getCallingUid();
440         final int pid = Binder.getCallingPid();
441         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
442 
443         final long token = Binder.clearCallingIdentity();
444         try {
445             synchronized (mLock) {
446                 registerManagerLocked(manager, uid, pid, packageName, userId);
447             }
448         } finally {
449             Binder.restoreCallingIdentity(token);
450         }
451     }
452 
unregisterManager(IMediaRouter2Manager manager)453     public void unregisterManager(IMediaRouter2Manager manager) {
454         Objects.requireNonNull(manager, "manager must not be null");
455 
456         final long token = Binder.clearCallingIdentity();
457         try {
458             synchronized (mLock) {
459                 unregisterManagerLocked(manager, false);
460             }
461         } finally {
462             Binder.restoreCallingIdentity(token);
463         }
464     }
465 
startScan(IMediaRouter2Manager manager)466     public void startScan(IMediaRouter2Manager manager) {
467         Objects.requireNonNull(manager, "manager must not be null");
468         final long token = Binder.clearCallingIdentity();
469         try {
470             synchronized (mLock) {
471                 startScanLocked(manager);
472             }
473         } finally {
474             Binder.restoreCallingIdentity(token);
475         }
476     }
477 
stopScan(IMediaRouter2Manager manager)478     public void stopScan(IMediaRouter2Manager manager) {
479         Objects.requireNonNull(manager, "manager must not be null");
480         final long token = Binder.clearCallingIdentity();
481         try {
482             synchronized (mLock) {
483                 stopScanLocked(manager);
484             }
485         } finally {
486             Binder.restoreCallingIdentity(token);
487         }
488     }
489 
setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, MediaRoute2Info route, int volume)490     public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId,
491             MediaRoute2Info route, int volume) {
492         Objects.requireNonNull(manager, "manager must not be null");
493         Objects.requireNonNull(route, "route must not be null");
494 
495         final long token = Binder.clearCallingIdentity();
496         try {
497             synchronized (mLock) {
498                 setRouteVolumeWithManagerLocked(requestId, manager, route, volume);
499             }
500         } finally {
501             Binder.restoreCallingIdentity(token);
502         }
503     }
504 
requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route)505     public void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
506             RoutingSessionInfo oldSession, MediaRoute2Info route) {
507         Objects.requireNonNull(manager, "manager must not be null");
508         Objects.requireNonNull(oldSession, "oldSession must not be null");
509 
510         final long token = Binder.clearCallingIdentity();
511         try {
512             synchronized (mLock) {
513                 requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
514             }
515         } finally {
516             Binder.restoreCallingIdentity(token);
517         }
518     }
519 
selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String uniqueSessionId, MediaRoute2Info route)520     public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
521             String uniqueSessionId, MediaRoute2Info route) {
522         Objects.requireNonNull(manager, "manager must not be null");
523         if (TextUtils.isEmpty(uniqueSessionId)) {
524             throw new IllegalArgumentException("uniqueSessionId must not be empty");
525         }
526         Objects.requireNonNull(route, "route must not be null");
527 
528         final long token = Binder.clearCallingIdentity();
529         try {
530             synchronized (mLock) {
531                 selectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
532             }
533         } finally {
534             Binder.restoreCallingIdentity(token);
535         }
536     }
537 
deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, String uniqueSessionId, MediaRoute2Info route)538     public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
539             String uniqueSessionId, MediaRoute2Info route) {
540         Objects.requireNonNull(manager, "manager must not be null");
541         if (TextUtils.isEmpty(uniqueSessionId)) {
542             throw new IllegalArgumentException("uniqueSessionId must not be empty");
543         }
544         Objects.requireNonNull(route, "route must not be null");
545 
546         final long token = Binder.clearCallingIdentity();
547         try {
548             synchronized (mLock) {
549                 deselectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
550             }
551         } finally {
552             Binder.restoreCallingIdentity(token);
553         }
554     }
555 
transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, String uniqueSessionId, MediaRoute2Info route)556     public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
557             String uniqueSessionId, MediaRoute2Info route) {
558         Objects.requireNonNull(manager, "manager must not be null");
559         if (TextUtils.isEmpty(uniqueSessionId)) {
560             throw new IllegalArgumentException("uniqueSessionId must not be empty");
561         }
562         Objects.requireNonNull(route, "route must not be null");
563 
564         final long token = Binder.clearCallingIdentity();
565         try {
566             synchronized (mLock) {
567                 transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
568             }
569         } finally {
570             Binder.restoreCallingIdentity(token);
571         }
572     }
573 
setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, String uniqueSessionId, int volume)574     public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
575             String uniqueSessionId, int volume) {
576         Objects.requireNonNull(manager, "manager must not be null");
577         if (TextUtils.isEmpty(uniqueSessionId)) {
578             throw new IllegalArgumentException("uniqueSessionId must not be empty");
579         }
580 
581         final long token = Binder.clearCallingIdentity();
582         try {
583             synchronized (mLock) {
584                 setSessionVolumeWithManagerLocked(requestId, manager, uniqueSessionId, volume);
585             }
586         } finally {
587             Binder.restoreCallingIdentity(token);
588         }
589     }
590 
releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String uniqueSessionId)591     public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId,
592             String uniqueSessionId) {
593         Objects.requireNonNull(manager, "manager must not be null");
594         if (TextUtils.isEmpty(uniqueSessionId)) {
595             throw new IllegalArgumentException("uniqueSessionId must not be empty");
596         }
597 
598         final long token = Binder.clearCallingIdentity();
599         try {
600             synchronized (mLock) {
601                 releaseSessionWithManagerLocked(requestId, manager, uniqueSessionId);
602             }
603         } finally {
604             Binder.restoreCallingIdentity(token);
605         }
606     }
607 
updateRunningUserAndProfiles(int newActiveUserId)608     /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
609         synchronized (mLock) {
610             if (mCurrentActiveUserId != newActiveUserId) {
611                 Slog.i(TAG, TextUtils.formatSimple(
612                         "switchUser | user: %d", newActiveUserId));
613 
614                 mCurrentActiveUserId = newActiveUserId;
615                 for (int i = 0; i < mUserRecords.size(); i++) {
616                     int userId = mUserRecords.keyAt(i);
617                     UserRecord userRecord = mUserRecords.valueAt(i);
618                     if (isUserActiveLocked(userId)) {
619                         // userId corresponds to the active user, or one of its profiles. We
620                         // ensure the associated structures are initialized.
621                         userRecord.mHandler.sendMessage(
622                                 obtainMessage(UserHandler::start, userRecord.mHandler));
623                     } else {
624                         userRecord.mHandler.sendMessage(
625                                 obtainMessage(UserHandler::stop, userRecord.mHandler));
626                         disposeUserIfNeededLocked(userRecord);
627                     }
628                 }
629             }
630         }
631     }
632 
routerDied(@onNull RouterRecord routerRecord)633     void routerDied(@NonNull RouterRecord routerRecord) {
634         synchronized (mLock) {
635             unregisterRouter2Locked(routerRecord.mRouter, true);
636         }
637     }
638 
managerDied(@onNull ManagerRecord managerRecord)639     void managerDied(@NonNull ManagerRecord managerRecord) {
640         synchronized (mLock) {
641             unregisterManagerLocked(managerRecord.mManager, true);
642         }
643     }
644 
645     /**
646      * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
647      * of the active user, returns {@code false} otherwise.
648      */
649     @GuardedBy("mLock")
isUserActiveLocked(int userId)650     private boolean isUserActiveLocked(int userId) {
651         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
652     }
653 
654     ////////////////////////////////////////////////////////////////
655     ////  ***Locked methods related to MediaRouter2
656     ////   - Should have @NonNull/@Nullable on all arguments
657     ////////////////////////////////////////////////////////////////
658 
659     @GuardedBy("mLock")
registerRouter2Locked(@onNull IMediaRouter2 router, int uid, int pid, @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission)660     private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
661             @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
662             boolean hasModifyAudioRoutingPermission) {
663         final IBinder binder = router.asBinder();
664         if (mAllRouterRecords.get(binder) != null) {
665             Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
666                     + packageName);
667             return;
668         }
669 
670         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
671         RouterRecord routerRecord = new RouterRecord(userRecord, router, uid, pid, packageName,
672                 hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
673         try {
674             binder.linkToDeath(routerRecord, 0);
675         } catch (RemoteException ex) {
676             throw new RuntimeException("MediaRouter2 died prematurely.", ex);
677         }
678 
679         userRecord.mRouterRecords.add(routerRecord);
680         mAllRouterRecords.put(binder, routerRecord);
681 
682         userRecord.mHandler.sendMessage(
683                 obtainMessage(UserHandler::notifyRouterRegistered,
684                         userRecord.mHandler, routerRecord));
685 
686         Slog.i(TAG, TextUtils.formatSimple(
687                 "registerRouter2 | package: %s, uid: %d, pid: %d, router: %d",
688                 packageName, uid, pid, routerRecord.mRouterId));
689     }
690 
691     @GuardedBy("mLock")
unregisterRouter2Locked(@onNull IMediaRouter2 router, boolean died)692     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
693         RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
694         if (routerRecord == null) {
695             Slog.w(TAG, "Ignoring unregistering unknown router2");
696             return;
697         }
698 
699         Slog.i(TAG, TextUtils.formatSimple(
700                 "unregisterRouter2 | package: %s, router: %d",
701                 routerRecord.mPackageName,
702                 routerRecord.mRouterId));
703 
704         UserRecord userRecord = routerRecord.mUserRecord;
705         userRecord.mRouterRecords.remove(routerRecord);
706         routerRecord.mUserRecord.mHandler.sendMessage(
707                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
708                         routerRecord.mUserRecord.mHandler,
709                         routerRecord.mPackageName, null));
710         userRecord.mHandler.sendMessage(
711                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
712                         userRecord.mHandler));
713         routerRecord.dispose();
714         disposeUserIfNeededLocked(userRecord); // since router removed from user
715     }
716 
setDiscoveryRequestWithRouter2Locked(@onNull RouterRecord routerRecord, @NonNull RouteDiscoveryPreference discoveryRequest)717     private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
718             @NonNull RouteDiscoveryPreference discoveryRequest) {
719         if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
720             return;
721         }
722 
723         Slog.i(TAG, TextUtils.formatSimple(
724                 "setDiscoveryRequestWithRouter2 | router: %d, discovery request: %s",
725                 routerRecord.mRouterId, discoveryRequest.toString()));
726 
727         routerRecord.mDiscoveryPreference = discoveryRequest;
728         routerRecord.mUserRecord.mHandler.sendMessage(
729                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
730                         routerRecord.mUserRecord.mHandler,
731                         routerRecord.mPackageName,
732                         routerRecord.mDiscoveryPreference));
733         routerRecord.mUserRecord.mHandler.sendMessage(
734                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
735                         routerRecord.mUserRecord.mHandler));
736     }
737 
setRouteVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)738     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
739             @NonNull MediaRoute2Info route, int volume) {
740         final IBinder binder = router.asBinder();
741         RouterRecord routerRecord = mAllRouterRecords.get(binder);
742 
743         if (routerRecord != null) {
744             Slog.i(TAG, TextUtils.formatSimple(
745                     "setRouteVolumeWithRouter2 | router: %d, volume: %d",
746                     routerRecord.mRouterId, volume));
747 
748             routerRecord.mUserRecord.mHandler.sendMessage(
749                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
750                             routerRecord.mUserRecord.mHandler,
751                             DUMMY_REQUEST_ID, route, volume));
752         }
753     }
754 
requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId, @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)755     private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
756             @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
757             @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
758         final IBinder binder = router.asBinder();
759         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
760 
761         if (routerRecord == null) {
762             return;
763         }
764 
765         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
766             ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
767                     toRequesterId(managerRequestId));
768             if (manager == null || manager.mLastSessionCreationRequest == null) {
769                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
770                         + "Ignoring unknown request.");
771                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
772                         routerRecord, requestId);
773                 return;
774             }
775             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
776                     oldSession.getId())) {
777                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
778                         + "Ignoring unmatched routing session.");
779                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
780                         routerRecord, requestId);
781                 return;
782             }
783             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
784                     route.getId())) {
785                 // When media router has no permission
786                 if (!routerRecord.mHasModifyAudioRoutingPermission
787                         && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
788                         && route.isSystemRoute()) {
789                     route = manager.mLastSessionCreationRequest.mRoute;
790                 } else {
791                     Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
792                             + "Ignoring unmatched route.");
793                     routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
794                             routerRecord, requestId);
795                     return;
796                 }
797             }
798             manager.mLastSessionCreationRequest = null;
799         } else {
800             if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
801                     && !TextUtils.equals(route.getId(),
802                     routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId())) {
803                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
804                         + route);
805                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
806                         routerRecord, requestId);
807                 return;
808             }
809         }
810 
811         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
812         routerRecord.mUserRecord.mHandler.sendMessage(
813                 obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
814                         routerRecord.mUserRecord.mHandler,
815                         uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
816                         sessionHints));
817     }
818 
selectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)819     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
820             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
821         final IBinder binder = router.asBinder();
822         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
823 
824         if (routerRecord == null) {
825             return;
826         }
827 
828         Slog.i(TAG, TextUtils.formatSimple(
829                 "selectRouteWithRouter2 | router: %d, route: %s",
830                 routerRecord.mRouterId, route.getId()));
831 
832         routerRecord.mUserRecord.mHandler.sendMessage(
833                 obtainMessage(UserHandler::selectRouteOnHandler,
834                         routerRecord.mUserRecord.mHandler,
835                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
836     }
837 
deselectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)838     private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
839             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
840         final IBinder binder = router.asBinder();
841         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
842 
843         if (routerRecord == null) {
844             return;
845         }
846 
847         Slog.i(TAG, TextUtils.formatSimple(
848                 "deselectRouteWithRouter2 | router: %d, route: %s",
849                 routerRecord.mRouterId, route.getId()));
850 
851         routerRecord.mUserRecord.mHandler.sendMessage(
852                 obtainMessage(UserHandler::deselectRouteOnHandler,
853                         routerRecord.mUserRecord.mHandler,
854                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
855     }
856 
transferToRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)857     private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
858             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
859         final IBinder binder = router.asBinder();
860         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
861 
862         if (routerRecord == null) {
863             return;
864         }
865 
866         Slog.i(TAG, TextUtils.formatSimple(
867                 "transferToRouteWithRouter2 | router: %d, route: %s",
868                 routerRecord.mRouterId, route.getId()));
869 
870         String defaultRouteId =
871                 routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
872         if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
873                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
874             routerRecord.mUserRecord.mHandler.sendMessage(
875                     obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
876                             routerRecord.mUserRecord.mHandler,
877                             routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
878         } else {
879             routerRecord.mUserRecord.mHandler.sendMessage(
880                     obtainMessage(UserHandler::transferToRouteOnHandler,
881                             routerRecord.mUserRecord.mHandler,
882                             DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
883         }
884     }
885 
setSessionVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)886     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
887             @NonNull String uniqueSessionId, int volume) {
888         final IBinder binder = router.asBinder();
889         RouterRecord routerRecord = mAllRouterRecords.get(binder);
890 
891         if (routerRecord == null) {
892             return;
893         }
894 
895         Slog.i(TAG, TextUtils.formatSimple(
896                 "setSessionVolumeWithRouter2 | router: %d, session: %s, volume: %d",
897                 routerRecord.mRouterId,  uniqueSessionId, volume));
898 
899         routerRecord.mUserRecord.mHandler.sendMessage(
900                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
901                         routerRecord.mUserRecord.mHandler,
902                         DUMMY_REQUEST_ID, uniqueSessionId, volume));
903     }
904 
releaseSessionWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)905     private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
906             @NonNull String uniqueSessionId) {
907         final IBinder binder = router.asBinder();
908         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
909 
910         if (routerRecord == null) {
911             return;
912         }
913 
914         Slog.i(TAG, TextUtils.formatSimple(
915                 "releaseSessionWithRouter2 | router: %d, session: %s",
916                 routerRecord.mRouterId,  uniqueSessionId));
917 
918         routerRecord.mUserRecord.mHandler.sendMessage(
919                 obtainMessage(UserHandler::releaseSessionOnHandler,
920                         routerRecord.mUserRecord.mHandler,
921                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
922     }
923 
924     ////////////////////////////////////////////////////////////
925     ////  ***Locked methods related to MediaRouter2Manager
926     ////   - Should have @NonNull/@Nullable on all arguments
927     ////////////////////////////////////////////////////////////
928 
getRemoteSessionsLocked( @onNull IMediaRouter2Manager manager)929     private List<RoutingSessionInfo> getRemoteSessionsLocked(
930             @NonNull IMediaRouter2Manager manager) {
931         final IBinder binder = manager.asBinder();
932         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
933 
934         if (managerRecord == null) {
935             Slog.w(TAG, "getRemoteSessionLocked: Ignoring unknown manager");
936             return Collections.emptyList();
937         }
938 
939         List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
940         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mRouteProviders) {
941             if (!provider.mIsSystemRouteProvider) {
942                 sessionInfos.addAll(provider.getSessionInfos());
943             }
944         }
945         return sessionInfos;
946     }
947 
948     @GuardedBy("mLock")
registerManagerLocked(@onNull IMediaRouter2Manager manager, int uid, int pid, @NonNull String packageName, int userId)949     private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
950             int uid, int pid, @NonNull String packageName, int userId) {
951         final IBinder binder = manager.asBinder();
952         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
953 
954         if (managerRecord != null) {
955             Slog.w(TAG, "registerManagerLocked: Same manager already exists. packageName="
956                     + packageName);
957             return;
958         }
959 
960         Slog.i(TAG, TextUtils.formatSimple(
961                 "registerManager | uid: %d, pid: %d, package: %s, user: %d",
962                 uid, pid, packageName, userId));
963 
964         mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
965                 "Must hold MEDIA_CONTENT_CONTROL permission.");
966 
967         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
968         managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName);
969         try {
970             binder.linkToDeath(managerRecord, 0);
971         } catch (RemoteException ex) {
972             throw new RuntimeException("Media router manager died prematurely.", ex);
973         }
974 
975         userRecord.mManagerRecords.add(managerRecord);
976         mAllManagerRecords.put(binder, managerRecord);
977 
978         // Note: Features should be sent first before the routes. If not, the
979         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
980         // due to the lack of features.
981         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
982             // TODO: UserRecord <-> routerRecord, why do they reference each other?
983             // How about removing mUserRecord from routerRecord?
984             routerRecord.mUserRecord.mHandler.sendMessage(
985                     obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
986                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
987         }
988 
989         userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager,
990                 userRecord.mHandler, manager));
991     }
992 
unregisterManagerLocked(@onNull IMediaRouter2Manager manager, boolean died)993     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
994         ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
995         if (managerRecord == null) {
996             return;
997         }
998         UserRecord userRecord = managerRecord.mUserRecord;
999 
1000         Slog.i(TAG, TextUtils.formatSimple(
1001                 "unregisterManager | package: %s, user: %d, manager: %d",
1002                 managerRecord.mPackageName,
1003                 userRecord.mUserId,
1004                 managerRecord.mManagerId));
1005 
1006         userRecord.mManagerRecords.remove(managerRecord);
1007         managerRecord.dispose();
1008         disposeUserIfNeededLocked(userRecord); // since manager removed from user
1009     }
1010 
startScanLocked(@onNull IMediaRouter2Manager manager)1011     private void startScanLocked(@NonNull IMediaRouter2Manager manager) {
1012         final IBinder binder = manager.asBinder();
1013         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1014         if (managerRecord == null) {
1015             return;
1016         }
1017 
1018         Slog.i(TAG, TextUtils.formatSimple(
1019                 "startScan | manager: %d", managerRecord.mManagerId));
1020 
1021         managerRecord.startScan();
1022     }
1023 
stopScanLocked(@onNull IMediaRouter2Manager manager)1024     private void stopScanLocked(@NonNull IMediaRouter2Manager manager) {
1025         final IBinder binder = manager.asBinder();
1026         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1027         if (managerRecord == null) {
1028             return;
1029         }
1030 
1031         Slog.i(TAG, TextUtils.formatSimple(
1032                 "stopScan | manager: %d", managerRecord.mManagerId));
1033 
1034         managerRecord.stopScan();
1035     }
1036 
setRouteVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull MediaRoute2Info route, int volume)1037     private void setRouteVolumeWithManagerLocked(int requestId,
1038             @NonNull IMediaRouter2Manager manager,
1039             @NonNull MediaRoute2Info route, int volume) {
1040         final IBinder binder = manager.asBinder();
1041         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1042 
1043         if (managerRecord == null) {
1044             return;
1045         }
1046 
1047         Slog.i(TAG, TextUtils.formatSimple(
1048                 "setRouteVolumeWithManager | manager: %d, route: %s, volume: %d",
1049                 managerRecord.mManagerId, route.getId(), volume));
1050 
1051         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1052         managerRecord.mUserRecord.mHandler.sendMessage(
1053                 obtainMessage(UserHandler::setRouteVolumeOnHandler,
1054                         managerRecord.mUserRecord.mHandler,
1055                         uniqueRequestId, route, volume));
1056     }
1057 
requestCreateSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)1058     private void requestCreateSessionWithManagerLocked(int requestId,
1059             @NonNull IMediaRouter2Manager manager,
1060             @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
1061         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
1062         if (managerRecord == null) {
1063             return;
1064         }
1065 
1066         Slog.i(TAG, TextUtils.formatSimple(
1067                 "requestCreateSessionWithManager | manager: %d, route: %s",
1068                 managerRecord.mManagerId, route.getId()));
1069 
1070         String packageName = oldSession.getClientPackageName();
1071 
1072         RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
1073         if (routerRecord == null) {
1074             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
1075                     + "unknown router.");
1076             try {
1077                 managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
1078             } catch (RemoteException ex) {
1079                 Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
1080                         + "Manager probably died.");
1081             }
1082             return;
1083         }
1084 
1085         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1086         if (managerRecord.mLastSessionCreationRequest != null) {
1087             managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
1088                     managerRecord.mManager,
1089                     toOriginalRequestId(managerRecord.mLastSessionCreationRequest
1090                             .mManagerRequestId),
1091                     REASON_UNKNOWN_ERROR);
1092             managerRecord.mLastSessionCreationRequest = null;
1093         }
1094         managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
1095                 MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
1096                 oldSession, route);
1097 
1098         // Before requesting to the provider, get session hints from the media router.
1099         // As a return, media router will request to create a session.
1100         routerRecord.mUserRecord.mHandler.sendMessage(
1101                 obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
1102                         routerRecord.mUserRecord.mHandler,
1103                         uniqueRequestId, routerRecord, managerRecord, oldSession, route));
1104     }
1105 
selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1106     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
1107             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1108         final IBinder binder = manager.asBinder();
1109         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1110 
1111         if (managerRecord == null) {
1112             return;
1113         }
1114 
1115         Slog.i(TAG, TextUtils.formatSimple(
1116                 "selectRouteWithManager | manager: %d, session: %s, route: %s",
1117                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1118 
1119         // Can be null if the session is system's or RCN.
1120         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1121                 .findRouterWithSessionLocked(uniqueSessionId);
1122 
1123         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1124         managerRecord.mUserRecord.mHandler.sendMessage(
1125                 obtainMessage(UserHandler::selectRouteOnHandler,
1126                         managerRecord.mUserRecord.mHandler,
1127                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1128     }
1129 
deselectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1130     private void deselectRouteWithManagerLocked(int requestId,
1131             @NonNull IMediaRouter2Manager manager,
1132             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1133         final IBinder binder = manager.asBinder();
1134         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1135 
1136         if (managerRecord == null) {
1137             return;
1138         }
1139 
1140         Slog.i(TAG, TextUtils.formatSimple(
1141                 "deselectRouteWithManager | manager: %d, session: %s, route: %s",
1142                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1143 
1144         // Can be null if the session is system's or RCN.
1145         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1146                 .findRouterWithSessionLocked(uniqueSessionId);
1147 
1148         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1149         managerRecord.mUserRecord.mHandler.sendMessage(
1150                 obtainMessage(UserHandler::deselectRouteOnHandler,
1151                         managerRecord.mUserRecord.mHandler,
1152                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1153     }
1154 
transferToRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1155     private void transferToRouteWithManagerLocked(int requestId,
1156             @NonNull IMediaRouter2Manager manager,
1157             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1158         final IBinder binder = manager.asBinder();
1159         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1160 
1161         if (managerRecord == null) {
1162             return;
1163         }
1164 
1165         Slog.i(TAG, TextUtils.formatSimple(
1166                 "transferToRouteWithManager | manager: %d, session: %s, route: %s",
1167                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1168 
1169         // Can be null if the session is system's or RCN.
1170         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1171                 .findRouterWithSessionLocked(uniqueSessionId);
1172 
1173         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1174         managerRecord.mUserRecord.mHandler.sendMessage(
1175                 obtainMessage(UserHandler::transferToRouteOnHandler,
1176                         managerRecord.mUserRecord.mHandler,
1177                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1178     }
1179 
setSessionVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, int volume)1180     private void setSessionVolumeWithManagerLocked(int requestId,
1181             @NonNull IMediaRouter2Manager manager,
1182             @NonNull String uniqueSessionId, int volume) {
1183         final IBinder binder = manager.asBinder();
1184         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1185 
1186         if (managerRecord == null) {
1187             return;
1188         }
1189 
1190         Slog.i(TAG, TextUtils.formatSimple(
1191                 "setSessionVolumeWithManager | manager: %d, session: %s, volume: %d",
1192                 managerRecord.mManagerId, uniqueSessionId, volume));
1193 
1194         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1195         managerRecord.mUserRecord.mHandler.sendMessage(
1196                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1197                         managerRecord.mUserRecord.mHandler,
1198                         uniqueRequestId, uniqueSessionId, volume));
1199     }
1200 
releaseSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId)1201     private void releaseSessionWithManagerLocked(int requestId,
1202             @NonNull IMediaRouter2Manager manager,
1203             @NonNull String uniqueSessionId) {
1204         final IBinder binder = manager.asBinder();
1205         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1206 
1207         if (managerRecord == null) {
1208             return;
1209         }
1210 
1211         Slog.i(TAG, TextUtils.formatSimple(
1212                 "releaseSessionWithManager | manager: %d, session: %s",
1213                 managerRecord.mManagerId, uniqueSessionId));
1214 
1215         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1216                 .findRouterWithSessionLocked(uniqueSessionId);
1217 
1218         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1219         managerRecord.mUserRecord.mHandler.sendMessage(
1220                 obtainMessage(UserHandler::releaseSessionOnHandler,
1221                         managerRecord.mUserRecord.mHandler,
1222                         uniqueRequestId, routerRecord, uniqueSessionId));
1223     }
1224 
1225     ////////////////////////////////////////////////////////////
1226     ////  ***Locked methods used by both router2 and manager
1227     ////   - Should have @NonNull/@Nullable on all arguments
1228     ////////////////////////////////////////////////////////////
1229 
1230     @GuardedBy("mLock")
getOrCreateUserRecordLocked(int userId)1231     private UserRecord getOrCreateUserRecordLocked(int userId) {
1232         UserRecord userRecord = mUserRecords.get(userId);
1233         if (userRecord == null) {
1234             userRecord = new UserRecord(userId);
1235             mUserRecords.put(userId, userRecord);
1236             userRecord.init();
1237             if (isUserActiveLocked(userId)) {
1238                 userRecord.mHandler.sendMessage(
1239                         obtainMessage(UserHandler::start, userRecord.mHandler));
1240             }
1241         }
1242         return userRecord;
1243     }
1244 
1245     @GuardedBy("mLock")
disposeUserIfNeededLocked(@onNull UserRecord userRecord)1246     private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
1247         // If there are no records left and the user is no longer current then go ahead
1248         // and purge the user record and all of its associated state.  If the user is current
1249         // then leave it alone since we might be connected to a route or want to query
1250         // the same route information again soon.
1251         if (!isUserActiveLocked(userRecord.mUserId)
1252                 && userRecord.mRouterRecords.isEmpty()
1253                 && userRecord.mManagerRecords.isEmpty()) {
1254             if (DEBUG) {
1255                 Slog.d(TAG, userRecord + ": Disposed");
1256             }
1257             userRecord.mHandler.sendMessage(
1258                     obtainMessage(UserHandler::stop, userRecord.mHandler));
1259             mUserRecords.remove(userRecord.mUserId);
1260             // Note: User already stopped (by switchUser) so no need to send stop message here.
1261         }
1262     }
1263 
toUniqueRequestId(int requesterId, int originalRequestId)1264     static long toUniqueRequestId(int requesterId, int originalRequestId) {
1265         return ((long) requesterId << 32) | originalRequestId;
1266     }
1267 
toRequesterId(long uniqueRequestId)1268     static int toRequesterId(long uniqueRequestId) {
1269         return (int) (uniqueRequestId >> 32);
1270     }
1271 
toOriginalRequestId(long uniqueRequestId)1272     static int toOriginalRequestId(long uniqueRequestId) {
1273         return (int) uniqueRequestId;
1274     }
1275 
1276     final class UserRecord {
1277         public final int mUserId;
1278         //TODO: make records private for thread-safety
1279         final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
1280         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
1281         RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
1282         final UserHandler mHandler;
1283 
UserRecord(int userId)1284         UserRecord(int userId) {
1285             mUserId = userId;
1286             mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
1287         }
1288 
init()1289         void init() {
1290             mHandler.init();
1291         }
1292 
1293         // TODO: This assumes that only one router exists in a package.
1294         //       Do this in Android S or later.
findRouterRecordLocked(String packageName)1295         RouterRecord findRouterRecordLocked(String packageName) {
1296             for (RouterRecord routerRecord : mRouterRecords) {
1297                 if (TextUtils.equals(routerRecord.mPackageName, packageName)) {
1298                     return routerRecord;
1299                 }
1300             }
1301             return null;
1302         }
1303     }
1304 
1305     final class RouterRecord implements IBinder.DeathRecipient {
1306         public final UserRecord mUserRecord;
1307         public final String mPackageName;
1308         public final List<Integer> mSelectRouteSequenceNumbers;
1309         public final IMediaRouter2 mRouter;
1310         public final int mUid;
1311         public final int mPid;
1312         public final boolean mHasConfigureWifiDisplayPermission;
1313         public final boolean mHasModifyAudioRoutingPermission;
1314         public final int mRouterId;
1315 
1316         public RouteDiscoveryPreference mDiscoveryPreference;
1317         public MediaRoute2Info mSelectedRoute;
1318 
RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid, String packageName, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission)1319         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
1320                 String packageName, boolean hasConfigureWifiDisplayPermission,
1321                 boolean hasModifyAudioRoutingPermission) {
1322             mUserRecord = userRecord;
1323             mPackageName = packageName;
1324             mSelectRouteSequenceNumbers = new ArrayList<>();
1325             mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
1326             mRouter = router;
1327             mUid = uid;
1328             mPid = pid;
1329             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
1330             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
1331             mRouterId = mNextRouterOrManagerId.getAndIncrement();
1332         }
1333 
dispose()1334         public void dispose() {
1335             mRouter.asBinder().unlinkToDeath(this, 0);
1336         }
1337 
1338         @Override
binderDied()1339         public void binderDied() {
1340             routerDied(this);
1341         }
1342     }
1343 
1344     final class ManagerRecord implements IBinder.DeathRecipient {
1345         public final UserRecord mUserRecord;
1346         public final IMediaRouter2Manager mManager;
1347         public final int mUid;
1348         public final int mPid;
1349         public final String mPackageName;
1350         public final int mManagerId;
1351         public SessionCreationRequest mLastSessionCreationRequest;
1352         public boolean mIsScanning;
1353 
ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager, int uid, int pid, String packageName)1354         ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
1355                 int uid, int pid, String packageName) {
1356             mUserRecord = userRecord;
1357             mManager = manager;
1358             mUid = uid;
1359             mPid = pid;
1360             mPackageName = packageName;
1361             mManagerId = mNextRouterOrManagerId.getAndIncrement();
1362         }
1363 
dispose()1364         public void dispose() {
1365             mManager.asBinder().unlinkToDeath(this, 0);
1366         }
1367 
1368         @Override
binderDied()1369         public void binderDied() {
1370             managerDied(this);
1371         }
1372 
dump(PrintWriter pw, String prefix)1373         public void dump(PrintWriter pw, String prefix) {
1374             pw.println(prefix + this);
1375         }
1376 
startScan()1377         public void startScan() {
1378             if (mIsScanning) {
1379                 return;
1380             }
1381             mIsScanning = true;
1382             mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
1383                     UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
1384         }
1385 
stopScan()1386         public void stopScan() {
1387             if (!mIsScanning) {
1388                 return;
1389             }
1390             mIsScanning = false;
1391             mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
1392                     UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
1393         }
1394 
1395         @Override
toString()1396         public String toString() {
1397             return "Manager " + mPackageName + " (pid " + mPid + ")";
1398         }
1399     }
1400 
1401     static final class UserHandler extends Handler implements
1402             MediaRoute2ProviderWatcher.Callback,
1403             MediaRoute2Provider.Callback {
1404 
1405         private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
1406         private final UserRecord mUserRecord;
1407         private final MediaRoute2ProviderWatcher mWatcher;
1408 
1409         private final SystemMediaRoute2Provider mSystemProvider;
1410         private final ArrayList<MediaRoute2Provider> mRouteProviders =
1411                 new ArrayList<>();
1412 
1413         private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
1414         private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
1415                 new CopyOnWriteArrayList<>();
1416         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
1417 
1418         private boolean mRunning;
1419 
1420         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler(@onNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord)1421         UserHandler(@NonNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord) {
1422             super(Looper.getMainLooper(), null, true);
1423             mServiceRef = new WeakReference<>(service);
1424             mUserRecord = userRecord;
1425             mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
1426                     UserHandle.of(userRecord.mUserId));
1427             mRouteProviders.add(mSystemProvider);
1428             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
1429                     this, mUserRecord.mUserId);
1430         }
1431 
init()1432         void init() {
1433             mSystemProvider.setCallback(this);
1434         }
1435 
start()1436         private void start() {
1437             if (!mRunning) {
1438                 mRunning = true;
1439                 mSystemProvider.start();
1440                 mWatcher.start();
1441             }
1442         }
1443 
stop()1444         private void stop() {
1445             if (mRunning) {
1446                 mRunning = false;
1447                 mWatcher.stop(); // also stops all providers
1448                 mSystemProvider.stop();
1449             }
1450         }
1451 
1452         @Override
onAddProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)1453         public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
1454             proxy.setCallback(this);
1455             mRouteProviders.add(proxy);
1456             proxy.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
1457         }
1458 
1459         @Override
onRemoveProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)1460         public void onRemoveProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
1461             mRouteProviders.remove(proxy);
1462         }
1463 
1464         @Override
onProviderStateChanged(@onNull MediaRoute2Provider provider)1465         public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
1466             sendMessage(PooledLambda.obtainMessage(UserHandler::onProviderStateChangedOnHandler,
1467                     this, provider));
1468         }
1469 
1470         @Override
onSessionCreated(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)1471         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
1472                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
1473             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
1474                     this, provider, uniqueRequestId, sessionInfo));
1475         }
1476 
1477         @Override
onSessionUpdated(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1478         public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
1479                 @NonNull RoutingSessionInfo sessionInfo) {
1480             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
1481                     this, provider, sessionInfo));
1482         }
1483 
1484         @Override
onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1485         public void onSessionReleased(@NonNull MediaRoute2Provider provider,
1486                 @NonNull RoutingSessionInfo sessionInfo) {
1487             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
1488                     this, provider, sessionInfo));
1489         }
1490 
1491         @Override
onRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)1492         public void onRequestFailed(@NonNull MediaRoute2Provider provider, long uniqueRequestId,
1493                 int reason) {
1494             sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
1495                     this, provider, uniqueRequestId, reason));
1496         }
1497 
1498         @Nullable
findRouterWithSessionLocked(@onNull String uniqueSessionId)1499         public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
1500             return mSessionToRouterMap.get(uniqueSessionId);
1501         }
1502 
1503         @Nullable
findManagerWithId(int managerId)1504         public ManagerRecord findManagerWithId(int managerId) {
1505             for (ManagerRecord manager : getManagerRecords()) {
1506                 if (manager.mManagerId == managerId) {
1507                     return manager;
1508                 }
1509             }
1510             return null;
1511         }
1512 
maybeUpdateDiscoveryPreferenceForUid(int uid)1513         public void maybeUpdateDiscoveryPreferenceForUid(int uid) {
1514             MediaRouter2ServiceImpl service = mServiceRef.get();
1515             if (service == null) {
1516                 return;
1517             }
1518             boolean isUidRelevant;
1519             synchronized (service.mLock) {
1520                 isUidRelevant = mUserRecord.mRouterRecords.stream().anyMatch(
1521                         router -> router.mUid == uid)
1522                         | mUserRecord.mManagerRecords.stream().anyMatch(
1523                             manager -> manager.mUid == uid);
1524             }
1525             if (isUidRelevant) {
1526                 sendMessage(PooledLambda.obtainMessage(
1527                         UserHandler::updateDiscoveryPreferenceOnHandler, this));
1528             }
1529         }
1530 
onProviderStateChangedOnHandler(@onNull MediaRoute2Provider provider)1531         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
1532             int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
1533             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
1534             MediaRoute2ProviderInfo prevInfo =
1535                     (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
1536             if (Objects.equals(prevInfo, currentInfo)) return;
1537 
1538             List<MediaRoute2Info> addedRoutes = new ArrayList<>();
1539             List<MediaRoute2Info> removedRoutes = new ArrayList<>();
1540             List<MediaRoute2Info> changedRoutes = new ArrayList<>();
1541             if (prevInfo == null) {
1542                 mLastProviderInfos.add(currentInfo);
1543                 addedRoutes.addAll(currentInfo.getRoutes());
1544             } else if (currentInfo == null) {
1545                 mLastProviderInfos.remove(prevInfo);
1546                 removedRoutes.addAll(prevInfo.getRoutes());
1547             } else {
1548                 mLastProviderInfos.set(providerInfoIndex, currentInfo);
1549                 final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
1550                 final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
1551 
1552                 for (MediaRoute2Info route : currentRoutes) {
1553                     if (!route.isValid()) {
1554                         Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
1555                                 + route);
1556                         continue;
1557                     }
1558                     MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
1559                     if (prevRoute == null) {
1560                         addedRoutes.add(route);
1561                     } else if (!Objects.equals(prevRoute, route)) {
1562                         changedRoutes.add(route);
1563                     }
1564                 }
1565 
1566                 for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
1567                     if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
1568                         removedRoutes.add(prevRoute);
1569                     }
1570                 }
1571             }
1572 
1573             List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
1574             List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
1575 
1576             if (!addedRoutes.isEmpty()) {
1577                 // If routes were added, currentInfo cannot be null.
1578                 Slog.i(TAG,
1579                         toLoggingMessage(
1580                                 /* source= */ "addProviderRoutes",
1581                                 currentInfo.getUniqueId(),
1582                                 (ArrayList) addedRoutes));
1583             }
1584             if (!removedRoutes.isEmpty()) {
1585                 // If routes were removed, prevInfo cannot be null.
1586                 Slog.i(TAG,
1587                         toLoggingMessage(
1588                                 /* source= */ "removeProviderRoutes",
1589                                 prevInfo.getUniqueId(),
1590                                 (ArrayList) removedRoutes));
1591             }
1592 
1593             List<IMediaRouter2Manager> managers = getManagers();
1594             List<MediaRoute2Info> defaultRoute = new ArrayList<>();
1595             defaultRoute.add(mSystemProvider.getDefaultRoute());
1596 
1597             if (addedRoutes.size() > 0) {
1598                 notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
1599                 if (!provider.mIsSystemRouteProvider) {
1600                     notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
1601                             addedRoutes);
1602                 } else if (prevInfo == null) {
1603                     notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
1604                             defaultRoute);
1605                 } // 'else' is handled as changed routes
1606                 notifyRoutesAddedToManagers(managers, addedRoutes);
1607             }
1608             if (removedRoutes.size() > 0) {
1609                 notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
1610                         removedRoutes);
1611                 if (!provider.mIsSystemRouteProvider) {
1612                     notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
1613                             removedRoutes);
1614                 }
1615                 notifyRoutesRemovedToManagers(managers, removedRoutes);
1616             }
1617             if (changedRoutes.size() > 0) {
1618                 notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
1619                         changedRoutes);
1620                 if (!provider.mIsSystemRouteProvider) {
1621                     notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
1622                             changedRoutes);
1623                 } else if (prevInfo != null) {
1624                     notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
1625                             defaultRoute);
1626                 } // 'else' is handled as added routes
1627                 notifyRoutesChangedToManagers(managers, changedRoutes);
1628             }
1629         }
1630 
toLoggingMessage( String source, String providerId, ArrayList<MediaRoute2Info> routes)1631         private static String toLoggingMessage(
1632                 String source, String providerId, ArrayList<MediaRoute2Info> routes) {
1633             String routesString =
1634                     routes.stream()
1635                             .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
1636                             .collect(Collectors.joining(/* delimiter= */ ", "));
1637             return TextUtils.formatSimple("%s | provider: %s, routes: [%s]",
1638                     source, providerId, routesString);
1639         }
1640 
getLastProviderInfoIndex(@onNull String providerId)1641         private int getLastProviderInfoIndex(@NonNull String providerId) {
1642             for (int i = 0; i < mLastProviderInfos.size(); i++) {
1643                 MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
1644                 if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
1645                     return i;
1646                 }
1647             }
1648             return -1;
1649         }
1650 
requestRouterCreateSessionOnHandler(long uniqueRequestId, @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)1651         private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
1652                 @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
1653                 @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
1654             try {
1655                 if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission) {
1656                     routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
1657                             oldSession, mSystemProvider.getDefaultRoute());
1658                 } else {
1659                     routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
1660                             oldSession, route);
1661                 }
1662             } catch (RemoteException ex) {
1663                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
1664                         + "Failed to request. Router probably died.", ex);
1665                 notifyRequestFailedToManager(managerRecord.mManager,
1666                         toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
1667             }
1668         }
1669 
requestCreateSessionWithRouter2OnHandler(long uniqueRequestId, long managerRequestId, @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)1670         private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
1671                 long managerRequestId, @NonNull RouterRecord routerRecord,
1672                 @NonNull RoutingSessionInfo oldSession,
1673                 @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
1674 
1675             final MediaRoute2Provider provider = findProvider(route.getProviderId());
1676             if (provider == null) {
1677                 Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session "
1678                         + "creation request since no provider found for given route=" + route);
1679                 notifySessionCreationFailedToRouter(routerRecord,
1680                         toOriginalRequestId(uniqueRequestId));
1681                 return;
1682             }
1683 
1684             SessionCreationRequest request =
1685                     new SessionCreationRequest(routerRecord, uniqueRequestId,
1686                             managerRequestId, oldSession, route);
1687             mSessionCreationRequests.add(request);
1688 
1689             provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
1690                     route.getOriginalId(), sessionHints);
1691         }
1692 
1693         // routerRecord can be null if the session is system's or RCN.
selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1694         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
1695                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1696             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
1697                     "selecting")) {
1698                 return;
1699             }
1700 
1701             final String providerId = route.getProviderId();
1702             final MediaRoute2Provider provider = findProvider(providerId);
1703             if (provider == null) {
1704                 return;
1705             }
1706             provider.selectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
1707                     route.getOriginalId());
1708         }
1709 
1710         // routerRecord can be null if the session is system's or RCN.
deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1711         private void deselectRouteOnHandler(long uniqueRequestId,
1712                 @Nullable RouterRecord routerRecord,
1713                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1714             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
1715                     "deselecting")) {
1716                 return;
1717             }
1718 
1719             final String providerId = route.getProviderId();
1720             final MediaRoute2Provider provider = findProvider(providerId);
1721             if (provider == null) {
1722                 return;
1723             }
1724 
1725             provider.deselectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
1726                     route.getOriginalId());
1727         }
1728 
1729         // routerRecord can be null if the session is system's or RCN.
transferToRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1730         private void transferToRouteOnHandler(long uniqueRequestId,
1731                 @Nullable RouterRecord routerRecord,
1732                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1733             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
1734                     "transferring to")) {
1735                 return;
1736             }
1737 
1738             final String providerId = route.getProviderId();
1739             final MediaRoute2Provider provider = findProvider(providerId);
1740             if (provider == null) {
1741                 return;
1742             }
1743             provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
1744                     route.getOriginalId());
1745         }
1746 
1747         // routerRecord is null if and only if the session is created without the request, which
1748         // includes the system's session and RCN cases.
checkArgumentsForSessionControl(@ullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description)1749         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
1750                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
1751                 @NonNull String description) {
1752             final String providerId = route.getProviderId();
1753             final MediaRoute2Provider provider = findProvider(providerId);
1754             if (provider == null) {
1755                 Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
1756                         + "given route=" + route);
1757                 return false;
1758             }
1759 
1760             // Bypass checking router if it's the system session (routerRecord should be null)
1761             if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
1762                 return true;
1763             }
1764 
1765             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
1766             if (matchingRecord != routerRecord) {
1767                 Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
1768                         + "packageName=" + routerRecord.mPackageName + " route=" + route);
1769                 return false;
1770             }
1771 
1772             final String sessionId = getOriginalId(uniqueSessionId);
1773             if (sessionId == null) {
1774                 Slog.w(TAG, "Failed to get original session id from unique session id. "
1775                         + "uniqueSessionId=" + uniqueSessionId);
1776                 return false;
1777             }
1778 
1779             return true;
1780         }
1781 
setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route, int volume)1782         private void setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route,
1783                 int volume) {
1784             final MediaRoute2Provider provider = findProvider(route.getProviderId());
1785             if (provider == null) {
1786                 Slog.w(TAG, "setRouteVolumeOnHandler: Couldn't find provider for route=" + route);
1787                 return;
1788             }
1789             provider.setRouteVolume(uniqueRequestId, route.getOriginalId(), volume);
1790         }
1791 
setSessionVolumeOnHandler(long uniqueRequestId, @NonNull String uniqueSessionId, int volume)1792         private void setSessionVolumeOnHandler(long uniqueRequestId,
1793                 @NonNull String uniqueSessionId, int volume) {
1794             final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
1795             if (provider == null) {
1796                 Slog.w(TAG, "setSessionVolumeOnHandler: Couldn't find provider for session id="
1797                         + uniqueSessionId);
1798                 return;
1799             }
1800             provider.setSessionVolume(uniqueRequestId, getOriginalId(uniqueSessionId), volume);
1801         }
1802 
releaseSessionOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId)1803         private void releaseSessionOnHandler(long uniqueRequestId,
1804                 @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
1805             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
1806             if (matchingRecord != routerRecord) {
1807                 Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName="
1808                         + (routerRecord == null ? null : routerRecord.mPackageName)
1809                         + " uniqueSessionId=" + uniqueSessionId);
1810                 return;
1811             }
1812 
1813             final String providerId = getProviderId(uniqueSessionId);
1814             if (providerId == null) {
1815                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
1816                         + "uniqueSessionId=" + uniqueSessionId);
1817                 return;
1818             }
1819 
1820             final String sessionId = getOriginalId(uniqueSessionId);
1821             if (sessionId == null) {
1822                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
1823                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
1824                 return;
1825             }
1826 
1827             final MediaRoute2Provider provider = findProvider(providerId);
1828             if (provider == null) {
1829                 Slog.w(TAG, "Ignoring releasing session since no provider found for given "
1830                         + "providerId=" + providerId);
1831                 return;
1832             }
1833 
1834             provider.releaseSession(uniqueRequestId, sessionId);
1835         }
1836 
onSessionCreatedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)1837         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
1838                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
1839             SessionCreationRequest matchingRequest = null;
1840 
1841             for (SessionCreationRequest request : mSessionCreationRequests) {
1842                 if (request.mUniqueRequestId == uniqueRequestId
1843                         && TextUtils.equals(
1844                         request.mRoute.getProviderId(), provider.getUniqueId())) {
1845                     matchingRequest = request;
1846                     break;
1847                 }
1848             }
1849 
1850             long managerRequestId = (matchingRequest == null)
1851                     ? MediaRoute2ProviderService.REQUEST_ID_NONE
1852                     : matchingRequest.mManagerRequestId;
1853             notifySessionCreatedToManagers(managerRequestId, sessionInfo);
1854 
1855             if (matchingRequest == null) {
1856                 Slog.w(TAG, "Ignoring session creation result for unknown request. "
1857                         + "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
1858                 return;
1859             }
1860 
1861             mSessionCreationRequests.remove(matchingRequest);
1862             // Not to show old session
1863             MediaRoute2Provider oldProvider =
1864                     findProvider(matchingRequest.mOldSession.getProviderId());
1865             if (oldProvider != null) {
1866                 oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
1867             } else {
1868                 Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
1869                         + "session=" + matchingRequest.mOldSession);
1870             }
1871 
1872             // Succeeded
1873             if (sessionInfo.isSystemSession()
1874                     && !matchingRequest.mRouterRecord.mHasModifyAudioRoutingPermission) {
1875                 notifySessionCreatedToRouter(matchingRequest.mRouterRecord,
1876                         toOriginalRequestId(uniqueRequestId),
1877                         mSystemProvider.getDefaultSessionInfo());
1878             } else {
1879                 notifySessionCreatedToRouter(matchingRequest.mRouterRecord,
1880                         toOriginalRequestId(uniqueRequestId), sessionInfo);
1881             }
1882             mSessionToRouterMap.put(sessionInfo.getId(), matchingRequest.mRouterRecord);
1883         }
1884 
onSessionInfoChangedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1885         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
1886                 @NonNull RoutingSessionInfo sessionInfo) {
1887             List<IMediaRouter2Manager> managers = getManagers();
1888             notifySessionUpdatedToManagers(managers, sessionInfo);
1889 
1890             // For system provider, notify all routers.
1891             if (provider == mSystemProvider) {
1892                 MediaRouter2ServiceImpl service = mServiceRef.get();
1893                 if (service == null) {
1894                     return;
1895                 }
1896                 notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
1897                 notifySessionInfoChangedToRouters(getRouters(false),
1898                         mSystemProvider.getDefaultSessionInfo());
1899                 return;
1900             }
1901 
1902             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
1903             if (routerRecord == null) {
1904                 Slog.w(TAG, "onSessionInfoChangedOnHandler: No matching router found for session="
1905                         + sessionInfo);
1906                 return;
1907             }
1908             notifySessionInfoChangedToRouter(routerRecord, sessionInfo);
1909         }
1910 
onSessionReleasedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1911         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
1912                 @NonNull RoutingSessionInfo sessionInfo) {
1913             List<IMediaRouter2Manager> managers = getManagers();
1914             notifySessionReleasedToManagers(managers, sessionInfo);
1915 
1916             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
1917             if (routerRecord == null) {
1918                 Slog.w(TAG, "onSessionReleasedOnHandler: No matching router found for session="
1919                         + sessionInfo);
1920                 return;
1921             }
1922             notifySessionReleasedToRouter(routerRecord, sessionInfo);
1923         }
1924 
onRequestFailedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)1925         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
1926                 long uniqueRequestId, int reason) {
1927             if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) {
1928                 return;
1929             }
1930 
1931             final int requesterId = toRequesterId(uniqueRequestId);
1932             ManagerRecord manager = findManagerWithId(requesterId);
1933             if (manager != null) {
1934                 notifyRequestFailedToManager(
1935                         manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
1936                 return;
1937             }
1938 
1939             // Currently, only the manager can get notified of failures.
1940             // TODO: Notify router too when the related callback is introduced.
1941         }
1942 
handleSessionCreationRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)1943         private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
1944                 long uniqueRequestId, int reason) {
1945             // Check whether the failure is about creating a session
1946             SessionCreationRequest matchingRequest = null;
1947             for (SessionCreationRequest request : mSessionCreationRequests) {
1948                 if (request.mUniqueRequestId == uniqueRequestId && TextUtils.equals(
1949                         request.mRoute.getProviderId(), provider.getUniqueId())) {
1950                     matchingRequest = request;
1951                     break;
1952                 }
1953             }
1954 
1955             if (matchingRequest == null) {
1956                 // The failure is not about creating a session.
1957                 return false;
1958             }
1959 
1960             mSessionCreationRequests.remove(matchingRequest);
1961 
1962             // Notify the requester about the failure.
1963             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
1964             if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
1965                 notifySessionCreationFailedToRouter(
1966                         matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
1967             } else {
1968                 final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
1969                 ManagerRecord manager = findManagerWithId(requesterId);
1970                 if (manager != null) {
1971                     notifyRequestFailedToManager(manager.mManager,
1972                             toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
1973                 }
1974             }
1975             return true;
1976         }
1977 
notifySessionCreatedToRouter(@onNull RouterRecord routerRecord, int requestId, @NonNull RoutingSessionInfo sessionInfo)1978         private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
1979                 int requestId, @NonNull RoutingSessionInfo sessionInfo) {
1980             try {
1981                 routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
1982             } catch (RemoteException ex) {
1983                 Slog.w(TAG, "Failed to notify router of the session creation."
1984                         + " Router probably died.", ex);
1985             }
1986         }
1987 
notifySessionCreationFailedToRouter(@onNull RouterRecord routerRecord, int requestId)1988         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
1989                 int requestId) {
1990             try {
1991                 routerRecord.mRouter.notifySessionCreated(requestId,
1992                         /* sessionInfo= */ null);
1993             } catch (RemoteException ex) {
1994                 Slog.w(TAG, "Failed to notify router of the session creation failure."
1995                         + " Router probably died.", ex);
1996             }
1997         }
1998 
notifySessionInfoChangedToRouter(@onNull RouterRecord routerRecord, @NonNull RoutingSessionInfo sessionInfo)1999         private void notifySessionInfoChangedToRouter(@NonNull RouterRecord routerRecord,
2000                 @NonNull RoutingSessionInfo sessionInfo) {
2001             try {
2002                 routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
2003             } catch (RemoteException ex) {
2004                 Slog.w(TAG, "Failed to notify router of the session info change."
2005                         + " Router probably died.", ex);
2006             }
2007         }
2008 
notifySessionReleasedToRouter(@onNull RouterRecord routerRecord, @NonNull RoutingSessionInfo sessionInfo)2009         private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
2010                 @NonNull RoutingSessionInfo sessionInfo) {
2011             try {
2012                 routerRecord.mRouter.notifySessionReleased(sessionInfo);
2013             } catch (RemoteException ex) {
2014                 Slog.w(TAG, "Failed to notify router of the session release."
2015                         + " Router probably died.", ex);
2016             }
2017         }
2018 
getAllRouters()2019         private List<IMediaRouter2> getAllRouters() {
2020             final List<IMediaRouter2> routers = new ArrayList<>();
2021             MediaRouter2ServiceImpl service = mServiceRef.get();
2022             if (service == null) {
2023                 return routers;
2024             }
2025             synchronized (service.mLock) {
2026                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
2027                     routers.add(routerRecord.mRouter);
2028                 }
2029             }
2030             return routers;
2031         }
2032 
getRouters(boolean hasModifyAudioRoutingPermission)2033         private List<IMediaRouter2> getRouters(boolean hasModifyAudioRoutingPermission) {
2034             final List<IMediaRouter2> routers = new ArrayList<>();
2035             MediaRouter2ServiceImpl service = mServiceRef.get();
2036             if (service == null) {
2037                 return routers;
2038             }
2039             synchronized (service.mLock) {
2040                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
2041                     if (hasModifyAudioRoutingPermission
2042                             == routerRecord.mHasModifyAudioRoutingPermission) {
2043                         routers.add(routerRecord.mRouter);
2044                     }
2045                 }
2046             }
2047             return routers;
2048         }
2049 
getManagers()2050         private List<IMediaRouter2Manager> getManagers() {
2051             final List<IMediaRouter2Manager> managers = new ArrayList<>();
2052             MediaRouter2ServiceImpl service = mServiceRef.get();
2053             if (service == null) {
2054                 return managers;
2055             }
2056             synchronized (service.mLock) {
2057                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
2058                     managers.add(managerRecord.mManager);
2059                 }
2060             }
2061             return managers;
2062         }
2063 
getRouterRecords()2064         private List<RouterRecord> getRouterRecords() {
2065             MediaRouter2ServiceImpl service = mServiceRef.get();
2066             if (service == null) {
2067                 return Collections.emptyList();
2068             }
2069             synchronized (service.mLock) {
2070                 return new ArrayList<>(mUserRecord.mRouterRecords);
2071             }
2072         }
2073 
getManagerRecords()2074         private List<ManagerRecord> getManagerRecords() {
2075             MediaRouter2ServiceImpl service = mServiceRef.get();
2076             if (service == null) {
2077                 return Collections.emptyList();
2078             }
2079             synchronized (service.mLock) {
2080                 return new ArrayList<>(mUserRecord.mManagerRecords);
2081             }
2082         }
2083 
notifyRouterRegistered(@onNull RouterRecord routerRecord)2084         private void notifyRouterRegistered(@NonNull RouterRecord routerRecord) {
2085             List<MediaRoute2Info> currentRoutes = new ArrayList<>();
2086 
2087             MediaRoute2ProviderInfo systemProviderInfo = null;
2088             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
2089                 // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
2090                 if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
2091                     // Adding routes from system provider will be handled below, so skip it here.
2092                     systemProviderInfo = providerInfo;
2093                     continue;
2094                 }
2095                 currentRoutes.addAll(providerInfo.getRoutes());
2096             }
2097 
2098             RoutingSessionInfo currentSystemSessionInfo;
2099             if (routerRecord.mHasModifyAudioRoutingPermission) {
2100                 if (systemProviderInfo != null) {
2101                     currentRoutes.addAll(systemProviderInfo.getRoutes());
2102                 } else {
2103                     // This shouldn't happen.
2104                     Slog.wtf(TAG, "System route provider not found.");
2105                 }
2106                 currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
2107             } else {
2108                 currentRoutes.add(mSystemProvider.getDefaultRoute());
2109                 currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
2110             }
2111 
2112             if (currentRoutes.size() == 0) {
2113                 return;
2114             }
2115 
2116             try {
2117                 routerRecord.mRouter.notifyRouterRegistered(
2118                         currentRoutes, currentSystemSessionInfo);
2119             } catch (RemoteException ex) {
2120                 Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
2121             }
2122         }
2123 
notifyRoutesAddedToRouters(@onNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes)2124         private void notifyRoutesAddedToRouters(@NonNull List<IMediaRouter2> routers,
2125                 @NonNull List<MediaRoute2Info> routes) {
2126             for (IMediaRouter2 router : routers) {
2127                 try {
2128                     router.notifyRoutesAdded(routes);
2129                 } catch (RemoteException ex) {
2130                     Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex);
2131                 }
2132             }
2133         }
2134 
notifyRoutesRemovedToRouters(@onNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes)2135         private void notifyRoutesRemovedToRouters(@NonNull List<IMediaRouter2> routers,
2136                 @NonNull List<MediaRoute2Info> routes) {
2137             for (IMediaRouter2 router : routers) {
2138                 try {
2139                     router.notifyRoutesRemoved(routes);
2140                 } catch (RemoteException ex) {
2141                     Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex);
2142                 }
2143             }
2144         }
2145 
notifyRoutesChangedToRouters(@onNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes)2146         private void notifyRoutesChangedToRouters(@NonNull List<IMediaRouter2> routers,
2147                 @NonNull List<MediaRoute2Info> routes) {
2148             for (IMediaRouter2 router : routers) {
2149                 try {
2150                     router.notifyRoutesChanged(routes);
2151                 } catch (RemoteException ex) {
2152                     Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex);
2153                 }
2154             }
2155         }
2156 
notifySessionInfoChangedToRouters(@onNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo)2157         private void notifySessionInfoChangedToRouters(@NonNull List<IMediaRouter2> routers,
2158                 @NonNull RoutingSessionInfo sessionInfo) {
2159             for (IMediaRouter2 router : routers) {
2160                 try {
2161                     router.notifySessionInfoChanged(sessionInfo);
2162                 } catch (RemoteException ex) {
2163                     Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
2164                 }
2165             }
2166         }
2167 
notifyRoutesToManager(@onNull IMediaRouter2Manager manager)2168         private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) {
2169             List<MediaRoute2Info> routes = new ArrayList<>();
2170             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
2171                 routes.addAll(providerInfo.getRoutes());
2172             }
2173             if (routes.size() == 0) {
2174                 return;
2175             }
2176             try {
2177                 manager.notifyRoutesAdded(routes);
2178             } catch (RemoteException ex) {
2179                 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
2180             }
2181         }
2182 
notifyRoutesAddedToManagers(@onNull List<IMediaRouter2Manager> managers, @NonNull List<MediaRoute2Info> routes)2183         private void notifyRoutesAddedToManagers(@NonNull List<IMediaRouter2Manager> managers,
2184                 @NonNull List<MediaRoute2Info> routes) {
2185             for (IMediaRouter2Manager manager : managers) {
2186                 try {
2187                     manager.notifyRoutesAdded(routes);
2188                 } catch (RemoteException ex) {
2189                     Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
2190                 }
2191             }
2192         }
2193 
notifyRoutesRemovedToManagers(@onNull List<IMediaRouter2Manager> managers, @NonNull List<MediaRoute2Info> routes)2194         private void notifyRoutesRemovedToManagers(@NonNull List<IMediaRouter2Manager> managers,
2195                 @NonNull List<MediaRoute2Info> routes) {
2196             for (IMediaRouter2Manager manager : managers) {
2197                 try {
2198                     manager.notifyRoutesRemoved(routes);
2199                 } catch (RemoteException ex) {
2200                     Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
2201                 }
2202             }
2203         }
2204 
notifyRoutesChangedToManagers(@onNull List<IMediaRouter2Manager> managers, @NonNull List<MediaRoute2Info> routes)2205         private void notifyRoutesChangedToManagers(@NonNull List<IMediaRouter2Manager> managers,
2206                 @NonNull List<MediaRoute2Info> routes) {
2207             for (IMediaRouter2Manager manager : managers) {
2208                 try {
2209                     manager.notifyRoutesChanged(routes);
2210                 } catch (RemoteException ex) {
2211                     Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
2212                 }
2213             }
2214         }
2215 
notifySessionCreatedToManagers(long managerRequestId, @NonNull RoutingSessionInfo session)2216         private void notifySessionCreatedToManagers(long managerRequestId,
2217                 @NonNull RoutingSessionInfo session) {
2218             int requesterId = toRequesterId(managerRequestId);
2219             int originalRequestId = toOriginalRequestId(managerRequestId);
2220 
2221             for (ManagerRecord manager : getManagerRecords()) {
2222                 try {
2223                     manager.mManager.notifySessionCreated(
2224                             ((manager.mManagerId == requesterId) ? originalRequestId :
2225                                     MediaRouter2Manager.REQUEST_ID_NONE), session);
2226                 } catch (RemoteException ex) {
2227                     Slog.w(TAG, "notifySessionCreatedToManagers: "
2228                             + "Failed to notify. Manager probably died.", ex);
2229                 }
2230             }
2231         }
2232 
notifySessionUpdatedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)2233         private void notifySessionUpdatedToManagers(
2234                 @NonNull List<IMediaRouter2Manager> managers,
2235                 @NonNull RoutingSessionInfo sessionInfo) {
2236             for (IMediaRouter2Manager manager : managers) {
2237                 try {
2238                     manager.notifySessionUpdated(sessionInfo);
2239                 } catch (RemoteException ex) {
2240                     Slog.w(TAG, "notifySessionUpdatedToManagers: "
2241                             + "Failed to notify. Manager probably died.", ex);
2242                 }
2243             }
2244         }
2245 
notifySessionReleasedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)2246         private void notifySessionReleasedToManagers(
2247                 @NonNull List<IMediaRouter2Manager> managers,
2248                 @NonNull RoutingSessionInfo sessionInfo) {
2249             for (IMediaRouter2Manager manager : managers) {
2250                 try {
2251                     manager.notifySessionReleased(sessionInfo);
2252                 } catch (RemoteException ex) {
2253                     Slog.w(TAG, "notifySessionReleasedToManagers: "
2254                             + "Failed to notify. Manager probably died.", ex);
2255                 }
2256             }
2257         }
2258 
notifyDiscoveryPreferenceChangedToManager(@onNull RouterRecord routerRecord, @NonNull IMediaRouter2Manager manager)2259         private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
2260                 @NonNull IMediaRouter2Manager manager) {
2261             try {
2262                 manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
2263                         routerRecord.mDiscoveryPreference);
2264             } catch (RemoteException ex) {
2265                 Slog.w(TAG, "Failed to notify preferred features changed."
2266                         + " Manager probably died.", ex);
2267             }
2268         }
2269 
notifyDiscoveryPreferenceChangedToManagers(@onNull String routerPackageName, @Nullable RouteDiscoveryPreference discoveryPreference)2270         private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
2271                 @Nullable RouteDiscoveryPreference discoveryPreference) {
2272             MediaRouter2ServiceImpl service = mServiceRef.get();
2273             if (service == null) {
2274                 return;
2275             }
2276             List<IMediaRouter2Manager> managers = new ArrayList<>();
2277             synchronized (service.mLock) {
2278                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
2279                     managers.add(managerRecord.mManager);
2280                 }
2281             }
2282             for (IMediaRouter2Manager manager : managers) {
2283                 try {
2284                     manager.notifyDiscoveryPreferenceChanged(routerPackageName,
2285                             discoveryPreference);
2286                 } catch (RemoteException ex) {
2287                     Slog.w(TAG, "Failed to notify preferred features changed."
2288                             + " Manager probably died.", ex);
2289                 }
2290             }
2291         }
2292 
notifyRequestFailedToManager(@onNull IMediaRouter2Manager manager, int requestId, int reason)2293         private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
2294                 int requestId, int reason) {
2295             try {
2296                 manager.notifyRequestFailed(requestId, reason);
2297             } catch (RemoteException ex) {
2298                 Slog.w(TAG, "Failed to notify manager of the request failure."
2299                         + " Manager probably died.", ex);
2300             }
2301         }
2302 
updateDiscoveryPreferenceOnHandler()2303         private void updateDiscoveryPreferenceOnHandler() {
2304             MediaRouter2ServiceImpl service = mServiceRef.get();
2305             if (service == null) {
2306                 return;
2307             }
2308             List<RouteDiscoveryPreference> discoveryPreferences = Collections.emptyList();
2309             List<RouterRecord> routerRecords = getRouterRecords();
2310             List<ManagerRecord> managerRecords = getManagerRecords();
2311 
2312             boolean shouldBindProviders = false;
2313 
2314             if (service.mPowerManager.isInteractive()) {
2315                 boolean isManagerScanning = managerRecords.stream().anyMatch(manager ->
2316                         manager.mIsScanning && service.mActivityManager
2317                                 .getPackageImportance(manager.mPackageName)
2318                                 <= PACKAGE_IMPORTANCE_FOR_DISCOVERY);
2319 
2320                 if (isManagerScanning) {
2321                     discoveryPreferences = routerRecords.stream()
2322                             .map(record -> record.mDiscoveryPreference)
2323                             .collect(Collectors.toList());
2324                     shouldBindProviders = true;
2325                 } else {
2326                     discoveryPreferences = routerRecords.stream().filter(record ->
2327                             service.mActivityManager.getPackageImportance(record.mPackageName)
2328                                     <= PACKAGE_IMPORTANCE_FOR_DISCOVERY)
2329                             .map(record -> record.mDiscoveryPreference)
2330                             .collect(Collectors.toList());
2331                 }
2332             }
2333 
2334             for (MediaRoute2Provider provider : mRouteProviders) {
2335                 if (provider instanceof MediaRoute2ProviderServiceProxy) {
2336                     ((MediaRoute2ProviderServiceProxy) provider)
2337                             .setManagerScanning(shouldBindProviders);
2338                 }
2339             }
2340 
2341             // Build a composite RouteDiscoveryPreference that matches all of the routes
2342             // that match one or more of the individual discovery preferences. It may also
2343             // match additional routes. The composite RouteDiscoveryPreference can be used
2344             // to query route providers once to obtain all of the routes of interest, which
2345             // can be subsequently filtered for the individual discovery preferences.
2346             Set<String> preferredFeatures = new HashSet<>();
2347             boolean activeScan = false;
2348             for (RouteDiscoveryPreference preference : discoveryPreferences) {
2349                 preferredFeatures.addAll(preference.getPreferredFeatures());
2350                 activeScan |= preference.shouldPerformActiveScan();
2351             }
2352             RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
2353                     List.copyOf(preferredFeatures), activeScan).build();
2354 
2355             synchronized (service.mLock) {
2356                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
2357                     return;
2358                 }
2359                 mUserRecord.mCompositeDiscoveryPreference = newPreference;
2360             }
2361             for (MediaRoute2Provider provider : mRouteProviders) {
2362                 provider.updateDiscoveryPreference(mUserRecord.mCompositeDiscoveryPreference);
2363             }
2364         }
2365 
findProvider(@ullable String providerId)2366         private MediaRoute2Provider findProvider(@Nullable String providerId) {
2367             for (MediaRoute2Provider provider : mRouteProviders) {
2368                 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
2369                     return provider;
2370                 }
2371             }
2372             return null;
2373         }
2374 
2375     }
2376     static final class SessionCreationRequest {
2377         public final RouterRecord mRouterRecord;
2378         public final long mUniqueRequestId;
2379         public final long mManagerRequestId;
2380         public final RoutingSessionInfo mOldSession;
2381         public final MediaRoute2Info mRoute;
2382 
SessionCreationRequest(@onNull RouterRecord routerRecord, long uniqueRequestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)2383         SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
2384                 long managerRequestId, @NonNull RoutingSessionInfo oldSession,
2385                 @NonNull MediaRoute2Info route) {
2386             mRouterRecord = routerRecord;
2387             mUniqueRequestId = uniqueRequestId;
2388             mManagerRequestId = managerRequestId;
2389             mOldSession = oldSession;
2390             mRoute = route;
2391         }
2392     }
2393 }
2394