• 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.annotation.RequiresPermission;
32 import android.app.ActivityManager;
33 import android.app.ActivityThread;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.IntentFilter;
39 import android.content.pm.PackageManager;
40 import android.media.IMediaRouter2;
41 import android.media.IMediaRouter2Manager;
42 import android.media.MediaRoute2Info;
43 import android.media.MediaRoute2ProviderInfo;
44 import android.media.MediaRoute2ProviderService;
45 import android.media.MediaRouter2Manager;
46 import android.media.RouteDiscoveryPreference;
47 import android.media.RouteListingPreference;
48 import android.media.RoutingSessionInfo;
49 import android.os.Binder;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Looper;
54 import android.os.PowerManager;
55 import android.os.RemoteException;
56 import android.os.UserHandle;
57 import android.provider.DeviceConfig;
58 import android.text.TextUtils;
59 import android.util.ArrayMap;
60 import android.util.Log;
61 import android.util.Slog;
62 import android.util.SparseArray;
63 
64 import com.android.internal.annotations.GuardedBy;
65 import com.android.internal.util.function.pooled.PooledLambda;
66 import com.android.server.LocalServices;
67 import com.android.server.pm.UserManagerInternal;
68 
69 import java.io.PrintWriter;
70 import java.lang.ref.WeakReference;
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.Collection;
74 import java.util.Collections;
75 import java.util.HashSet;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.Objects;
79 import java.util.Optional;
80 import java.util.Set;
81 import java.util.concurrent.CopyOnWriteArrayList;
82 import java.util.concurrent.atomic.AtomicBoolean;
83 import java.util.concurrent.atomic.AtomicInteger;
84 import java.util.stream.Collectors;
85 
86 /**
87  * Implements features related to {@link android.media.MediaRouter2} and
88  * {@link android.media.MediaRouter2Manager}.
89  */
90 class MediaRouter2ServiceImpl {
91     private static final String TAG = "MR2ServiceImpl";
92     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
93 
94     // TODO: (In Android S or later) if we add callback methods for generic failures
95     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
96     private static final long DUMMY_REQUEST_ID = -1;
97 
98     private static final String MEDIA_BETTER_TOGETHER_NAMESPACE = "media_better_together";
99 
100     private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
101             "scanning_package_minimum_importance";
102 
103     /**
104      * Contains the list of bluetooth permissions that are required to do system routing.
105      *
106      * <p>Alternatively, apps that hold {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} are
107      * also allowed to do system routing.
108      */
109     private static final String[] BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING =
110             new String[] {
111                 Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
112             };
113 
114     private static int sPackageImportanceForScanning = DeviceConfig.getInt(
115             MEDIA_BETTER_TOGETHER_NAMESPACE,
116             /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
117             /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
118 
119     private final Context mContext;
120     private final UserManagerInternal mUserManagerInternal;
121     private final Object mLock = new Object();
122     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
123     final ActivityManager mActivityManager;
124     final PowerManager mPowerManager;
125 
126     @GuardedBy("mLock")
127     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
128     @GuardedBy("mLock")
129     private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
130     @GuardedBy("mLock")
131     private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
132     @GuardedBy("mLock")
133     private int mCurrentActiveUserId = -1;
134 
135     private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
136             (uid, importance) -> {
137                 synchronized (mLock) {
138                     final int count = mUserRecords.size();
139                     for (int i = 0; i < count; i++) {
140                         mUserRecords.valueAt(i).mHandler.maybeUpdateDiscoveryPreferenceForUid(uid);
141                     }
142                 }
143             };
144 
145     private final BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() {
146         @Override
147         public void onReceive(Context context, Intent intent) {
148             synchronized (mLock) {
149                 final int count = mUserRecords.size();
150                 for (int i = 0; i < count; i++) {
151                     UserHandler userHandler = mUserRecords.valueAt(i).mHandler;
152                     userHandler.sendMessage(PooledLambda.obtainMessage(
153                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
154                 }
155             }
156         }
157     };
158 
159     @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
MediaRouter2ServiceImpl(Context context)160     /* package */ MediaRouter2ServiceImpl(Context context) {
161         mContext = context;
162         mActivityManager = mContext.getSystemService(ActivityManager.class);
163         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
164                 sPackageImportanceForScanning);
165         mPowerManager = mContext.getSystemService(PowerManager.class);
166         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
167 
168         IntentFilter screenOnOffIntentFilter = new IntentFilter();
169         screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
170         screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
171 
172         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
173         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
174 
175         DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
176                 ActivityThread.currentApplication().getMainExecutor(),
177                 this::onDeviceConfigChange);
178     }
179 
180     /**
181      * Called when there's a change in the permissions of an app.
182      *
183      * @param uid The uid of the app whose permissions changed.
184      */
onPermissionsChanged(int uid)185     private void onPermissionsChanged(int uid) {
186         synchronized (mLock) {
187             Optional<RouterRecord> affectedRouter =
188                     mAllRouterRecords.values().stream().filter(it -> it.mUid == uid).findFirst();
189             if (affectedRouter.isPresent()) {
190                 affectedRouter.get().maybeUpdateSystemRoutingPermissionLocked();
191             }
192         }
193     }
194 
195     // Start of methods that implement MediaRouter2 operations.
196 
197     @NonNull
verifyPackageExists(@onNull String clientPackageName)198     public boolean verifyPackageExists(@NonNull String clientPackageName) {
199         final int pid = Binder.getCallingPid();
200         final int uid = Binder.getCallingUid();
201         final long token = Binder.clearCallingIdentity();
202 
203         try {
204             mContext.enforcePermission(
205                     Manifest.permission.MEDIA_CONTENT_CONTROL,
206                     pid,
207                     uid,
208                     "Must hold MEDIA_CONTENT_CONTROL permission.");
209             PackageManager pm = mContext.getPackageManager();
210             pm.getPackageInfo(clientPackageName, PackageManager.PackageInfoFlags.of(0));
211             return true;
212         } catch (PackageManager.NameNotFoundException ex) {
213             return false;
214         } finally {
215             Binder.restoreCallingIdentity(token);
216         }
217     }
218 
219     @NonNull
getSystemRoutes()220     public List<MediaRoute2Info> getSystemRoutes() {
221         final int uid = Binder.getCallingUid();
222         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
223         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
224                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
225                 == PackageManager.PERMISSION_GRANTED;
226 
227         final long token = Binder.clearCallingIdentity();
228         try {
229             Collection<MediaRoute2Info> systemRoutes;
230             synchronized (mLock) {
231                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
232                 if (hasModifyAudioRoutingPermission) {
233                     MediaRoute2ProviderInfo providerInfo =
234                             userRecord.mHandler.mSystemProvider.getProviderInfo();
235                     if (providerInfo != null) {
236                         systemRoutes = providerInfo.getRoutes();
237                     } else {
238                         systemRoutes = Collections.emptyList();
239                     }
240                 } else {
241                     systemRoutes = new ArrayList<>();
242                     systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
243                 }
244             }
245             return new ArrayList<>(systemRoutes);
246         } finally {
247             Binder.restoreCallingIdentity(token);
248         }
249     }
250 
registerRouter2(@onNull IMediaRouter2 router, @NonNull String packageName)251     public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) {
252         Objects.requireNonNull(router, "router must not be null");
253         if (TextUtils.isEmpty(packageName)) {
254             throw new IllegalArgumentException("packageName must not be empty");
255         }
256 
257         final int uid = Binder.getCallingUid();
258         final int pid = Binder.getCallingPid();
259         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
260         final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
261                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
262                 == PackageManager.PERMISSION_GRANTED;
263         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
264                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
265                 == PackageManager.PERMISSION_GRANTED;
266 
267         final long token = Binder.clearCallingIdentity();
268         try {
269             synchronized (mLock) {
270                 registerRouter2Locked(router, uid, pid, packageName, userId,
271                         hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
272             }
273         } finally {
274             Binder.restoreCallingIdentity(token);
275         }
276     }
277 
unregisterRouter2(@onNull IMediaRouter2 router)278     public void unregisterRouter2(@NonNull IMediaRouter2 router) {
279         Objects.requireNonNull(router, "router must not be null");
280 
281         final long token = Binder.clearCallingIdentity();
282         try {
283             synchronized (mLock) {
284                 unregisterRouter2Locked(router, false);
285             }
286         } finally {
287             Binder.restoreCallingIdentity(token);
288         }
289     }
290 
setDiscoveryRequestWithRouter2(@onNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference)291     public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
292             @NonNull RouteDiscoveryPreference preference) {
293         Objects.requireNonNull(router, "router must not be null");
294         Objects.requireNonNull(preference, "preference must not be null");
295 
296         final long token = Binder.clearCallingIdentity();
297         try {
298             synchronized (mLock) {
299                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
300                 if (routerRecord == null) {
301                     Slog.w(TAG, "Ignoring updating discoveryRequest of null routerRecord.");
302                     return;
303                 }
304                 setDiscoveryRequestWithRouter2Locked(routerRecord, preference);
305             }
306         } finally {
307             Binder.restoreCallingIdentity(token);
308         }
309     }
310 
setRouteListingPreference( @onNull IMediaRouter2 router, @Nullable RouteListingPreference routeListingPreference)311     public void setRouteListingPreference(
312             @NonNull IMediaRouter2 router,
313             @Nullable RouteListingPreference routeListingPreference) {
314         ComponentName linkedItemLandingComponent =
315                 routeListingPreference != null
316                         ? routeListingPreference.getLinkedItemComponentName()
317                         : null;
318         if (linkedItemLandingComponent != null) {
319             int callingUid = Binder.getCallingUid();
320             MediaServerUtils.enforcePackageName(
321                     linkedItemLandingComponent.getPackageName(), callingUid);
322             if (!MediaServerUtils.isValidActivityComponentName(
323                     mContext,
324                     linkedItemLandingComponent,
325                     RouteListingPreference.ACTION_TRANSFER_MEDIA,
326                     Binder.getCallingUserHandle())) {
327                 throw new IllegalArgumentException(
328                         "Unable to resolve "
329                                 + linkedItemLandingComponent
330                                 + " to a valid activity for "
331                                 + RouteListingPreference.ACTION_TRANSFER_MEDIA);
332             }
333         }
334 
335         final long token = Binder.clearCallingIdentity();
336         try {
337             synchronized (mLock) {
338                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
339                 if (routerRecord == null) {
340                     Slog.w(TAG, "Ignoring updating route listing of null routerRecord.");
341                     return;
342                 }
343                 setRouteListingPreferenceLocked(routerRecord, routeListingPreference);
344             }
345         } finally {
346             Binder.restoreCallingIdentity(token);
347         }
348     }
349 
setRouteVolumeWithRouter2(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)350     public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
351             @NonNull MediaRoute2Info route, int volume) {
352         Objects.requireNonNull(router, "router must not be null");
353         Objects.requireNonNull(route, "route must not be null");
354 
355         final long token = Binder.clearCallingIdentity();
356         try {
357             synchronized (mLock) {
358                 setRouteVolumeWithRouter2Locked(router, route, volume);
359             }
360         } finally {
361             Binder.restoreCallingIdentity(token);
362         }
363     }
364 
requestCreateSessionWithRouter2(@onNull IMediaRouter2 router, int requestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, Bundle sessionHints)365     public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId,
366             long managerRequestId, @NonNull RoutingSessionInfo oldSession,
367             @NonNull MediaRoute2Info route, Bundle sessionHints) {
368         Objects.requireNonNull(router, "router must not be null");
369         Objects.requireNonNull(oldSession, "oldSession must not be null");
370         Objects.requireNonNull(route, "route must not be null");
371 
372         final long token = Binder.clearCallingIdentity();
373         try {
374             synchronized (mLock) {
375                 requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
376                         router, oldSession, route, sessionHints);
377             }
378         } finally {
379             Binder.restoreCallingIdentity(token);
380         }
381     }
382 
selectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)383     public void selectRouteWithRouter2(@NonNull IMediaRouter2 router,
384             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
385         Objects.requireNonNull(router, "router must not be null");
386         Objects.requireNonNull(route, "route must not be null");
387         if (TextUtils.isEmpty(uniqueSessionId)) {
388             throw new IllegalArgumentException("uniqueSessionId must not be empty");
389         }
390 
391         final long token = Binder.clearCallingIdentity();
392         try {
393             synchronized (mLock) {
394                 selectRouteWithRouter2Locked(router, uniqueSessionId, route);
395             }
396         } finally {
397             Binder.restoreCallingIdentity(token);
398         }
399     }
400 
deselectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)401     public void deselectRouteWithRouter2(@NonNull IMediaRouter2 router,
402             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
403         Objects.requireNonNull(router, "router must not be null");
404         Objects.requireNonNull(route, "route must not be null");
405         if (TextUtils.isEmpty(uniqueSessionId)) {
406             throw new IllegalArgumentException("uniqueSessionId must not be empty");
407         }
408 
409         final long token = Binder.clearCallingIdentity();
410         try {
411             synchronized (mLock) {
412                 deselectRouteWithRouter2Locked(router, uniqueSessionId, route);
413             }
414         } finally {
415             Binder.restoreCallingIdentity(token);
416         }
417     }
418 
transferToRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)419     public void transferToRouteWithRouter2(@NonNull IMediaRouter2 router,
420             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
421         Objects.requireNonNull(router, "router must not be null");
422         Objects.requireNonNull(route, "route must not be null");
423         if (TextUtils.isEmpty(uniqueSessionId)) {
424             throw new IllegalArgumentException("uniqueSessionId must not be empty");
425         }
426 
427         final long token = Binder.clearCallingIdentity();
428         try {
429             synchronized (mLock) {
430                 transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
431             }
432         } finally {
433             Binder.restoreCallingIdentity(token);
434         }
435     }
436 
setSessionVolumeWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)437     public void setSessionVolumeWithRouter2(@NonNull IMediaRouter2 router,
438             @NonNull String uniqueSessionId, int volume) {
439         Objects.requireNonNull(router, "router must not be null");
440         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
441         if (TextUtils.isEmpty(uniqueSessionId)) {
442             throw new IllegalArgumentException("uniqueSessionId must not be empty");
443         }
444 
445         final long token = Binder.clearCallingIdentity();
446         try {
447             synchronized (mLock) {
448                 setSessionVolumeWithRouter2Locked(router, uniqueSessionId, volume);
449             }
450         } finally {
451             Binder.restoreCallingIdentity(token);
452         }
453     }
454 
releaseSessionWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)455     public void releaseSessionWithRouter2(@NonNull IMediaRouter2 router,
456             @NonNull String uniqueSessionId) {
457         Objects.requireNonNull(router, "router must not be null");
458         if (TextUtils.isEmpty(uniqueSessionId)) {
459             throw new IllegalArgumentException("uniqueSessionId must not be empty");
460         }
461 
462         final long token = Binder.clearCallingIdentity();
463         try {
464             synchronized (mLock) {
465                 releaseSessionWithRouter2Locked(router, uniqueSessionId);
466             }
467         } finally {
468             Binder.restoreCallingIdentity(token);
469         }
470     }
471 
472     // End of methods that implement MediaRouter2 operations.
473 
474     // Start of methods that implement MediaRouter2Manager operations.
475 
476     @NonNull
getRemoteSessions(@onNull IMediaRouter2Manager manager)477     public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) {
478         Objects.requireNonNull(manager, "manager must not be null");
479         final long token = Binder.clearCallingIdentity();
480         try {
481             synchronized (mLock) {
482                 return getRemoteSessionsLocked(manager);
483             }
484         } finally {
485             Binder.restoreCallingIdentity(token);
486         }
487     }
488 
registerManager(@onNull IMediaRouter2Manager manager, @NonNull String packageName)489     public void registerManager(@NonNull IMediaRouter2Manager manager,
490             @NonNull String packageName) {
491         Objects.requireNonNull(manager, "manager must not be null");
492         if (TextUtils.isEmpty(packageName)) {
493             throw new IllegalArgumentException("packageName must not be empty");
494         }
495 
496         final int uid = Binder.getCallingUid();
497         final int pid = Binder.getCallingPid();
498         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
499 
500         final long token = Binder.clearCallingIdentity();
501         try {
502             synchronized (mLock) {
503                 registerManagerLocked(manager, uid, pid, packageName, userId);
504             }
505         } finally {
506             Binder.restoreCallingIdentity(token);
507         }
508     }
509 
unregisterManager(@onNull IMediaRouter2Manager manager)510     public void unregisterManager(@NonNull IMediaRouter2Manager manager) {
511         Objects.requireNonNull(manager, "manager must not be null");
512 
513         final long token = Binder.clearCallingIdentity();
514         try {
515             synchronized (mLock) {
516                 unregisterManagerLocked(manager, false);
517             }
518         } finally {
519             Binder.restoreCallingIdentity(token);
520         }
521     }
522 
startScan(@onNull IMediaRouter2Manager manager)523     public void startScan(@NonNull IMediaRouter2Manager manager) {
524         Objects.requireNonNull(manager, "manager must not be null");
525         final long token = Binder.clearCallingIdentity();
526         try {
527             synchronized (mLock) {
528                 startScanLocked(manager);
529             }
530         } finally {
531             Binder.restoreCallingIdentity(token);
532         }
533     }
534 
stopScan(@onNull IMediaRouter2Manager manager)535     public void stopScan(@NonNull IMediaRouter2Manager manager) {
536         Objects.requireNonNull(manager, "manager must not be null");
537         final long token = Binder.clearCallingIdentity();
538         try {
539             synchronized (mLock) {
540                 stopScanLocked(manager);
541             }
542         } finally {
543             Binder.restoreCallingIdentity(token);
544         }
545     }
546 
setRouteVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull MediaRoute2Info route, int volume)547     public void setRouteVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
548             @NonNull MediaRoute2Info route, int volume) {
549         Objects.requireNonNull(manager, "manager must not be null");
550         Objects.requireNonNull(route, "route must not be null");
551 
552         final long token = Binder.clearCallingIdentity();
553         try {
554             synchronized (mLock) {
555                 setRouteVolumeWithManagerLocked(requestId, manager, route, volume);
556             }
557         } finally {
558             Binder.restoreCallingIdentity(token);
559         }
560     }
561 
requestCreateSessionWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)562     public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager,
563             int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
564         Objects.requireNonNull(manager, "manager must not be null");
565         Objects.requireNonNull(oldSession, "oldSession must not be null");
566         Objects.requireNonNull(route, "route must not be null");
567 
568         final long token = Binder.clearCallingIdentity();
569         try {
570             synchronized (mLock) {
571                 requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
572             }
573         } finally {
574             Binder.restoreCallingIdentity(token);
575         }
576     }
577 
selectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)578     public void selectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
579             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
580         Objects.requireNonNull(manager, "manager must not be null");
581         if (TextUtils.isEmpty(uniqueSessionId)) {
582             throw new IllegalArgumentException("uniqueSessionId must not be empty");
583         }
584         Objects.requireNonNull(route, "route must not be null");
585 
586         final long token = Binder.clearCallingIdentity();
587         try {
588             synchronized (mLock) {
589                 selectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
590             }
591         } finally {
592             Binder.restoreCallingIdentity(token);
593         }
594     }
595 
deselectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)596     public void deselectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
597             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
598         Objects.requireNonNull(manager, "manager must not be null");
599         if (TextUtils.isEmpty(uniqueSessionId)) {
600             throw new IllegalArgumentException("uniqueSessionId must not be empty");
601         }
602         Objects.requireNonNull(route, "route must not be null");
603 
604         final long token = Binder.clearCallingIdentity();
605         try {
606             synchronized (mLock) {
607                 deselectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
608             }
609         } finally {
610             Binder.restoreCallingIdentity(token);
611         }
612     }
613 
transferToRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)614     public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
615             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
616         Objects.requireNonNull(manager, "manager must not be null");
617         if (TextUtils.isEmpty(uniqueSessionId)) {
618             throw new IllegalArgumentException("uniqueSessionId must not be empty");
619         }
620         Objects.requireNonNull(route, "route must not be null");
621 
622         final long token = Binder.clearCallingIdentity();
623         try {
624             synchronized (mLock) {
625                 transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
626             }
627         } finally {
628             Binder.restoreCallingIdentity(token);
629         }
630     }
631 
setSessionVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, int volume)632     public void setSessionVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
633             @NonNull String uniqueSessionId, int volume) {
634         Objects.requireNonNull(manager, "manager must not be null");
635         if (TextUtils.isEmpty(uniqueSessionId)) {
636             throw new IllegalArgumentException("uniqueSessionId must not be empty");
637         }
638 
639         final long token = Binder.clearCallingIdentity();
640         try {
641             synchronized (mLock) {
642                 setSessionVolumeWithManagerLocked(requestId, manager, uniqueSessionId, volume);
643             }
644         } finally {
645             Binder.restoreCallingIdentity(token);
646         }
647     }
648 
releaseSessionWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId)649     public void releaseSessionWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
650             @NonNull String uniqueSessionId) {
651         Objects.requireNonNull(manager, "manager must not be null");
652         if (TextUtils.isEmpty(uniqueSessionId)) {
653             throw new IllegalArgumentException("uniqueSessionId must not be empty");
654         }
655 
656         final long token = Binder.clearCallingIdentity();
657         try {
658             synchronized (mLock) {
659                 releaseSessionWithManagerLocked(requestId, manager, uniqueSessionId);
660             }
661         } finally {
662             Binder.restoreCallingIdentity(token);
663         }
664     }
665 
666     // End of methods that implement MediaRouter2Manager operations.
667 
668     // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
669 
670     @Nullable
getSystemSessionInfo( @ullable String packageName, boolean setDeviceRouteSelected)671     public RoutingSessionInfo getSystemSessionInfo(
672             @Nullable String packageName, boolean setDeviceRouteSelected) {
673         final int uid = Binder.getCallingUid();
674         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
675         final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
676                 android.Manifest.permission.MODIFY_AUDIO_ROUTING)
677                 == PackageManager.PERMISSION_GRANTED;
678 
679         final long token = Binder.clearCallingIdentity();
680         try {
681             RoutingSessionInfo systemSessionInfo = null;
682             synchronized (mLock) {
683                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
684                 List<RoutingSessionInfo> sessionInfos;
685                 if (hasModifyAudioRoutingPermission) {
686                     if (setDeviceRouteSelected) {
687                         systemSessionInfo = userRecord.mHandler.mSystemProvider
688                                 .generateDeviceRouteSelectedSessionInfo(packageName);
689                     } else {
690                         sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
691                         if (sessionInfos != null && !sessionInfos.isEmpty()) {
692                             systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0))
693                                     .setClientPackageName(packageName).build();
694                         } else {
695                             Slog.w(TAG, "System provider does not have any session info.");
696                         }
697                     }
698                 } else {
699                     systemSessionInfo = new RoutingSessionInfo.Builder(
700                             userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
701                             .setClientPackageName(packageName).build();
702                 }
703             }
704             return systemSessionInfo;
705         } finally {
706             Binder.restoreCallingIdentity(token);
707         }
708     }
709 
710     // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
711 
dump(@onNull PrintWriter pw, @NonNull String prefix)712     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
713         pw.println(prefix + "MediaRouter2ServiceImpl");
714 
715         String indent = prefix + "  ";
716 
717         synchronized (mLock) {
718             pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
719             pw.println(indent + "mCurrentActiveUserId=" + mCurrentActiveUserId);
720 
721             pw.println(indent + "UserRecords:");
722             if (mUserRecords.size() > 0) {
723                 for (int i = 0; i < mUserRecords.size(); i++) {
724                     mUserRecords.valueAt(i).dump(pw, indent + "  ");
725                 }
726             } else {
727                 pw.println(indent + "  <no user records>");
728             }
729         }
730     }
731 
updateRunningUserAndProfiles(int newActiveUserId)732     /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
733         synchronized (mLock) {
734             if (mCurrentActiveUserId != newActiveUserId) {
735                 Slog.i(TAG, TextUtils.formatSimple(
736                         "switchUser | user: %d", newActiveUserId));
737 
738                 mCurrentActiveUserId = newActiveUserId;
739                 // disposeUserIfNeededLocked might modify the collection, hence clone
740                 final var userRecords = mUserRecords.clone();
741                 for (int i = 0; i < userRecords.size(); i++) {
742                     int userId = userRecords.keyAt(i);
743                     UserRecord userRecord = userRecords.valueAt(i);
744                     if (isUserActiveLocked(userId)) {
745                         // userId corresponds to the active user, or one of its profiles. We
746                         // ensure the associated structures are initialized.
747                         userRecord.mHandler.sendMessage(
748                                 obtainMessage(UserHandler::start, userRecord.mHandler));
749                     } else {
750                         userRecord.mHandler.sendMessage(
751                                 obtainMessage(UserHandler::stop, userRecord.mHandler));
752                         disposeUserIfNeededLocked(userRecord);
753                     }
754                 }
755             }
756         }
757     }
758 
routerDied(@onNull RouterRecord routerRecord)759     void routerDied(@NonNull RouterRecord routerRecord) {
760         synchronized (mLock) {
761             unregisterRouter2Locked(routerRecord.mRouter, true);
762         }
763     }
764 
managerDied(@onNull ManagerRecord managerRecord)765     void managerDied(@NonNull ManagerRecord managerRecord) {
766         synchronized (mLock) {
767             unregisterManagerLocked(managerRecord.mManager, true);
768         }
769     }
770 
771     /**
772      * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
773      * of the active user, returns {@code false} otherwise.
774      */
775     @GuardedBy("mLock")
isUserActiveLocked(int userId)776     private boolean isUserActiveLocked(int userId) {
777         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
778     }
779 
780     // Start of locked methods that are used by MediaRouter2.
781 
782     @GuardedBy("mLock")
registerRouter2Locked(@onNull IMediaRouter2 router, int uid, int pid, @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission)783     private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
784             @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
785             boolean hasModifyAudioRoutingPermission) {
786         final IBinder binder = router.asBinder();
787         if (mAllRouterRecords.get(binder) != null) {
788             Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
789                     + packageName);
790             return;
791         }
792 
793         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
794         RouterRecord routerRecord = new RouterRecord(userRecord, router, uid, pid, packageName,
795                 hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission);
796         try {
797             binder.linkToDeath(routerRecord, 0);
798         } catch (RemoteException ex) {
799             throw new RuntimeException("MediaRouter2 died prematurely.", ex);
800         }
801 
802         userRecord.mRouterRecords.add(routerRecord);
803         mAllRouterRecords.put(binder, routerRecord);
804 
805         userRecord.mHandler.sendMessage(
806                 obtainMessage(UserHandler::notifyRouterRegistered,
807                         userRecord.mHandler, routerRecord));
808 
809         Slog.i(TAG, TextUtils.formatSimple(
810                 "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d",
811                 packageName, uid, pid, routerRecord.mRouterId));
812     }
813 
814     @GuardedBy("mLock")
unregisterRouter2Locked(@onNull IMediaRouter2 router, boolean died)815     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
816         RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
817         if (routerRecord == null) {
818             Slog.w(TAG, "Ignoring unregistering unknown router2");
819             return;
820         }
821 
822         Slog.i(
823                 TAG,
824                 TextUtils.formatSimple(
825                         "unregisterRouter2 | package: %s, router id: %d",
826                         routerRecord.mPackageName, routerRecord.mRouterId));
827 
828         UserRecord userRecord = routerRecord.mUserRecord;
829         userRecord.mRouterRecords.remove(routerRecord);
830         routerRecord.mUserRecord.mHandler.sendMessage(
831                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
832                         routerRecord.mUserRecord.mHandler,
833                         routerRecord.mPackageName, null));
834         routerRecord.mUserRecord.mHandler.sendMessage(
835                 obtainMessage(
836                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
837                         routerRecord.mUserRecord.mHandler,
838                         routerRecord.mPackageName,
839                         /* routeListingPreference= */ null));
840         userRecord.mHandler.sendMessage(
841                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
842                         userRecord.mHandler));
843         routerRecord.dispose();
844         disposeUserIfNeededLocked(userRecord); // since router removed from user
845     }
846 
setDiscoveryRequestWithRouter2Locked(@onNull RouterRecord routerRecord, @NonNull RouteDiscoveryPreference discoveryRequest)847     private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
848             @NonNull RouteDiscoveryPreference discoveryRequest) {
849         if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
850             return;
851         }
852 
853         Slog.i(
854                 TAG,
855                 TextUtils.formatSimple(
856                         "setDiscoveryRequestWithRouter2 | router: %s(id: %d), discovery request:"
857                             + " %s",
858                         routerRecord.mPackageName,
859                         routerRecord.mRouterId,
860                         discoveryRequest.toString()));
861 
862         routerRecord.mDiscoveryPreference = discoveryRequest;
863         routerRecord.mUserRecord.mHandler.sendMessage(
864                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
865                         routerRecord.mUserRecord.mHandler,
866                         routerRecord.mPackageName,
867                         routerRecord.mDiscoveryPreference));
868         routerRecord.mUserRecord.mHandler.sendMessage(
869                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
870                         routerRecord.mUserRecord.mHandler));
871     }
872 
873     @GuardedBy("mLock")
setRouteListingPreferenceLocked( RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference)874     private void setRouteListingPreferenceLocked(
875             RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference) {
876         routerRecord.mRouteListingPreference = routeListingPreference;
877         String routeListingAsString =
878                 routeListingPreference != null
879                         ? routeListingPreference.getItems().stream()
880                                 .map(RouteListingPreference.Item::getRouteId)
881                                 .collect(Collectors.joining(","))
882                         : null;
883 
884         Slog.i(
885                 TAG,
886                 TextUtils.formatSimple(
887                         "setRouteListingPreference | router: %s(id: %d), route listing preference:"
888                             + " [%s]",
889                         routerRecord.mPackageName, routerRecord.mRouterId, routeListingAsString));
890 
891         routerRecord.mUserRecord.mHandler.sendMessage(
892                 obtainMessage(
893                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
894                         routerRecord.mUserRecord.mHandler,
895                         routerRecord.mPackageName,
896                         routeListingPreference));
897     }
898 
setRouteVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)899     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
900             @NonNull MediaRoute2Info route, int volume) {
901         final IBinder binder = router.asBinder();
902         RouterRecord routerRecord = mAllRouterRecords.get(binder);
903 
904         if (routerRecord != null) {
905             Slog.i(
906                     TAG,
907                     TextUtils.formatSimple(
908                             "setRouteVolumeWithRouter2 | router: %s(id: %d), volume: %d",
909                             routerRecord.mPackageName, routerRecord.mRouterId, volume));
910 
911             routerRecord.mUserRecord.mHandler.sendMessage(
912                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
913                             routerRecord.mUserRecord.mHandler,
914                             DUMMY_REQUEST_ID, route, volume));
915         }
916     }
917 
requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId, @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)918     private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
919             @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
920             @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
921         final IBinder binder = router.asBinder();
922         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
923 
924         if (routerRecord == null) {
925             return;
926         }
927 
928         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
929             ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId(
930                     toRequesterId(managerRequestId));
931             if (manager == null || manager.mLastSessionCreationRequest == null) {
932                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
933                         + "Ignoring unknown request.");
934                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
935                         routerRecord, requestId);
936                 return;
937             }
938             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
939                     oldSession.getId())) {
940                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
941                         + "Ignoring unmatched routing session.");
942                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
943                         routerRecord, requestId);
944                 return;
945             }
946             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
947                     route.getId())) {
948                 // When media router has no permission
949                 if (!routerRecord.hasSystemRoutingPermission()
950                         && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
951                         && route.isSystemRoute()) {
952                     route = manager.mLastSessionCreationRequest.mRoute;
953                 } else {
954                     Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
955                             + "Ignoring unmatched route.");
956                     routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
957                             routerRecord, requestId);
958                     return;
959                 }
960             }
961             manager.mLastSessionCreationRequest = null;
962         } else {
963             if (route.isSystemRoute()
964                     && !routerRecord.hasSystemRoutingPermission()
965                     && !TextUtils.equals(
966                             route.getId(),
967                             routerRecord
968                                     .mUserRecord
969                                     .mHandler
970                                     .mSystemProvider
971                                     .getDefaultRoute()
972                                     .getId())) {
973                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
974                         + route);
975                 routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter(
976                         routerRecord, requestId);
977                 return;
978             }
979         }
980 
981         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
982         routerRecord.mUserRecord.mHandler.sendMessage(
983                 obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
984                         routerRecord.mUserRecord.mHandler,
985                         uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
986                         sessionHints));
987     }
988 
selectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)989     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
990             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
991         final IBinder binder = router.asBinder();
992         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
993 
994         if (routerRecord == null) {
995             return;
996         }
997 
998         Slog.i(
999                 TAG,
1000                 TextUtils.formatSimple(
1001                         "selectRouteWithRouter2 | router: %s(id: %d), route: %s",
1002                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1003 
1004         routerRecord.mUserRecord.mHandler.sendMessage(
1005                 obtainMessage(UserHandler::selectRouteOnHandler,
1006                         routerRecord.mUserRecord.mHandler,
1007                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1008     }
1009 
deselectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1010     private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1011             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1012         final IBinder binder = router.asBinder();
1013         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1014 
1015         if (routerRecord == null) {
1016             return;
1017         }
1018 
1019         Slog.i(
1020                 TAG,
1021                 TextUtils.formatSimple(
1022                         "deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
1023                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1024 
1025         routerRecord.mUserRecord.mHandler.sendMessage(
1026                 obtainMessage(UserHandler::deselectRouteOnHandler,
1027                         routerRecord.mUserRecord.mHandler,
1028                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1029     }
1030 
transferToRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1031     private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1032             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1033         final IBinder binder = router.asBinder();
1034         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1035 
1036         if (routerRecord == null) {
1037             return;
1038         }
1039 
1040         Slog.i(
1041                 TAG,
1042                 TextUtils.formatSimple(
1043                         "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
1044                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1045 
1046         String defaultRouteId =
1047                 routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
1048         if (route.isSystemRoute()
1049                 && !routerRecord.hasSystemRoutingPermission()
1050                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
1051             routerRecord.mUserRecord.mHandler.sendMessage(
1052                     obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
1053                             routerRecord.mUserRecord.mHandler,
1054                             routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
1055         } else {
1056             routerRecord.mUserRecord.mHandler.sendMessage(
1057                     obtainMessage(UserHandler::transferToRouteOnHandler,
1058                             routerRecord.mUserRecord.mHandler,
1059                             DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1060         }
1061     }
1062 
setSessionVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)1063     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
1064             @NonNull String uniqueSessionId, int volume) {
1065         final IBinder binder = router.asBinder();
1066         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1067 
1068         if (routerRecord == null) {
1069             return;
1070         }
1071 
1072         Slog.i(
1073                 TAG,
1074                 TextUtils.formatSimple(
1075                         "setSessionVolumeWithRouter2 | router: %s(id: %d), session: %s, volume: %d",
1076                         routerRecord.mPackageName,
1077                         routerRecord.mRouterId,
1078                         uniqueSessionId,
1079                         volume));
1080 
1081         routerRecord.mUserRecord.mHandler.sendMessage(
1082                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1083                         routerRecord.mUserRecord.mHandler,
1084                         DUMMY_REQUEST_ID, uniqueSessionId, volume));
1085     }
1086 
releaseSessionWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)1087     private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
1088             @NonNull String uniqueSessionId) {
1089         final IBinder binder = router.asBinder();
1090         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1091 
1092         if (routerRecord == null) {
1093             return;
1094         }
1095 
1096         Slog.i(
1097                 TAG,
1098                 TextUtils.formatSimple(
1099                         "releaseSessionWithRouter2 | router: %s(id: %d), session: %s",
1100                         routerRecord.mPackageName, routerRecord.mRouterId, uniqueSessionId));
1101 
1102         routerRecord.mUserRecord.mHandler.sendMessage(
1103                 obtainMessage(UserHandler::releaseSessionOnHandler,
1104                         routerRecord.mUserRecord.mHandler,
1105                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
1106     }
1107 
1108     // End of locked methods that are used by MediaRouter2.
1109 
1110     // Start of locked methods that are used by MediaRouter2Manager.
1111 
getRemoteSessionsLocked( @onNull IMediaRouter2Manager manager)1112     private List<RoutingSessionInfo> getRemoteSessionsLocked(
1113             @NonNull IMediaRouter2Manager manager) {
1114         final IBinder binder = manager.asBinder();
1115         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1116 
1117         if (managerRecord == null) {
1118             Slog.w(TAG, "getRemoteSessionLocked: Ignoring unknown manager");
1119             return Collections.emptyList();
1120         }
1121 
1122         List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
1123         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mRouteProviders) {
1124             if (!provider.mIsSystemRouteProvider) {
1125                 sessionInfos.addAll(provider.getSessionInfos());
1126             }
1127         }
1128         return sessionInfos;
1129     }
1130 
1131     @GuardedBy("mLock")
registerManagerLocked(@onNull IMediaRouter2Manager manager, int uid, int pid, @NonNull String packageName, int userId)1132     private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
1133             int uid, int pid, @NonNull String packageName, int userId) {
1134         final IBinder binder = manager.asBinder();
1135         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1136 
1137         if (managerRecord != null) {
1138             Slog.w(TAG, "registerManagerLocked: Same manager already exists. packageName="
1139                     + packageName);
1140             return;
1141         }
1142 
1143         Slog.i(TAG, TextUtils.formatSimple(
1144                 "registerManager | uid: %d, pid: %d, package: %s, user: %d",
1145                 uid, pid, packageName, userId));
1146 
1147         mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
1148                 "Must hold MEDIA_CONTENT_CONTROL permission.");
1149 
1150         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
1151         managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName);
1152         try {
1153             binder.linkToDeath(managerRecord, 0);
1154         } catch (RemoteException ex) {
1155             throw new RuntimeException("Media router manager died prematurely.", ex);
1156         }
1157 
1158         userRecord.mManagerRecords.add(managerRecord);
1159         mAllManagerRecords.put(binder, managerRecord);
1160 
1161         // Note: Features should be sent first before the routes. If not, the
1162         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
1163         // due to the lack of features.
1164         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
1165             // Send route listing preferences before discovery preferences and routes to avoid an
1166             // inconsistent state where there are routes to show, but the manager thinks
1167             // the app has not expressed a preference for listing.
1168             userRecord.mHandler.sendMessage(
1169                     obtainMessage(
1170                             UserHandler::notifyRouteListingPreferenceChangeToManagers,
1171                             routerRecord.mUserRecord.mHandler,
1172                             routerRecord.mPackageName,
1173                             routerRecord.mRouteListingPreference));
1174             // TODO: UserRecord <-> routerRecord, why do they reference each other?
1175             // How about removing mUserRecord from routerRecord?
1176             routerRecord.mUserRecord.mHandler.sendMessage(
1177                     obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager,
1178                         routerRecord.mUserRecord.mHandler, routerRecord, manager));
1179         }
1180 
1181         userRecord.mHandler.sendMessage(
1182                 obtainMessage(
1183                         UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
1184     }
1185 
unregisterManagerLocked(@onNull IMediaRouter2Manager manager, boolean died)1186     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
1187         ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
1188         if (managerRecord == null) {
1189             return;
1190         }
1191         UserRecord userRecord = managerRecord.mUserRecord;
1192 
1193         Slog.i(TAG, TextUtils.formatSimple(
1194                 "unregisterManager | package: %s, user: %d, manager: %d",
1195                 managerRecord.mPackageName,
1196                 userRecord.mUserId,
1197                 managerRecord.mManagerId));
1198 
1199         userRecord.mManagerRecords.remove(managerRecord);
1200         managerRecord.dispose();
1201         disposeUserIfNeededLocked(userRecord); // since manager removed from user
1202     }
1203 
startScanLocked(@onNull IMediaRouter2Manager manager)1204     private void startScanLocked(@NonNull IMediaRouter2Manager manager) {
1205         final IBinder binder = manager.asBinder();
1206         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1207         if (managerRecord == null) {
1208             return;
1209         }
1210 
1211         Slog.i(TAG, TextUtils.formatSimple(
1212                 "startScan | manager: %d", managerRecord.mManagerId));
1213 
1214         managerRecord.startScan();
1215     }
1216 
stopScanLocked(@onNull IMediaRouter2Manager manager)1217     private void stopScanLocked(@NonNull IMediaRouter2Manager manager) {
1218         final IBinder binder = manager.asBinder();
1219         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1220         if (managerRecord == null) {
1221             return;
1222         }
1223 
1224         Slog.i(TAG, TextUtils.formatSimple(
1225                 "stopScan | manager: %d", managerRecord.mManagerId));
1226 
1227         managerRecord.stopScan();
1228     }
1229 
setRouteVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull MediaRoute2Info route, int volume)1230     private void setRouteVolumeWithManagerLocked(int requestId,
1231             @NonNull IMediaRouter2Manager manager,
1232             @NonNull MediaRoute2Info route, int volume) {
1233         final IBinder binder = manager.asBinder();
1234         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1235 
1236         if (managerRecord == null) {
1237             return;
1238         }
1239 
1240         Slog.i(TAG, TextUtils.formatSimple(
1241                 "setRouteVolumeWithManager | manager: %d, route: %s, volume: %d",
1242                 managerRecord.mManagerId, route.getId(), volume));
1243 
1244         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1245         managerRecord.mUserRecord.mHandler.sendMessage(
1246                 obtainMessage(UserHandler::setRouteVolumeOnHandler,
1247                         managerRecord.mUserRecord.mHandler,
1248                         uniqueRequestId, route, volume));
1249     }
1250 
requestCreateSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)1251     private void requestCreateSessionWithManagerLocked(int requestId,
1252             @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession,
1253             @NonNull MediaRoute2Info route) {
1254         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
1255         if (managerRecord == null) {
1256             return;
1257         }
1258 
1259         Slog.i(TAG, TextUtils.formatSimple(
1260                 "requestCreateSessionWithManager | manager: %d, route: %s",
1261                 managerRecord.mManagerId, route.getId()));
1262 
1263         String packageName = oldSession.getClientPackageName();
1264 
1265         RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
1266         if (routerRecord == null) {
1267             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
1268                     + "unknown router.");
1269             try {
1270                 managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
1271             } catch (RemoteException ex) {
1272                 Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
1273                         + "Manager probably died.");
1274             }
1275             return;
1276         }
1277 
1278         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1279         if (managerRecord.mLastSessionCreationRequest != null) {
1280             managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
1281                     managerRecord.mManager,
1282                     toOriginalRequestId(managerRecord.mLastSessionCreationRequest
1283                             .mManagerRequestId),
1284                     REASON_UNKNOWN_ERROR);
1285             managerRecord.mLastSessionCreationRequest = null;
1286         }
1287         managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
1288                 MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
1289                 oldSession, route);
1290 
1291         // Before requesting to the provider, get session hints from the media router.
1292         // As a return, media router will request to create a session.
1293         routerRecord.mUserRecord.mHandler.sendMessage(
1294                 obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
1295                         routerRecord.mUserRecord.mHandler,
1296                         uniqueRequestId, routerRecord, managerRecord, oldSession, route));
1297     }
1298 
selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1299     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
1300             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1301         final IBinder binder = manager.asBinder();
1302         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1303 
1304         if (managerRecord == null) {
1305             return;
1306         }
1307 
1308         Slog.i(TAG, TextUtils.formatSimple(
1309                 "selectRouteWithManager | manager: %d, session: %s, route: %s",
1310                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1311 
1312         // Can be null if the session is system's or RCN.
1313         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1314                 .findRouterWithSessionLocked(uniqueSessionId);
1315 
1316         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1317         managerRecord.mUserRecord.mHandler.sendMessage(
1318                 obtainMessage(UserHandler::selectRouteOnHandler,
1319                         managerRecord.mUserRecord.mHandler,
1320                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1321     }
1322 
deselectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1323     private void deselectRouteWithManagerLocked(int requestId,
1324             @NonNull IMediaRouter2Manager manager,
1325             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1326         final IBinder binder = manager.asBinder();
1327         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1328 
1329         if (managerRecord == null) {
1330             return;
1331         }
1332 
1333         Slog.i(TAG, TextUtils.formatSimple(
1334                 "deselectRouteWithManager | manager: %d, session: %s, route: %s",
1335                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1336 
1337         // Can be null if the session is system's or RCN.
1338         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1339                 .findRouterWithSessionLocked(uniqueSessionId);
1340 
1341         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1342         managerRecord.mUserRecord.mHandler.sendMessage(
1343                 obtainMessage(UserHandler::deselectRouteOnHandler,
1344                         managerRecord.mUserRecord.mHandler,
1345                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1346     }
1347 
transferToRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1348     private void transferToRouteWithManagerLocked(int requestId,
1349             @NonNull IMediaRouter2Manager manager,
1350             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1351         final IBinder binder = manager.asBinder();
1352         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1353 
1354         if (managerRecord == null) {
1355             return;
1356         }
1357 
1358         Slog.i(TAG, TextUtils.formatSimple(
1359                 "transferToRouteWithManager | manager: %d, session: %s, route: %s",
1360                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1361 
1362         // Can be null if the session is system's or RCN.
1363         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1364                 .findRouterWithSessionLocked(uniqueSessionId);
1365 
1366         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1367         managerRecord.mUserRecord.mHandler.sendMessage(
1368                 obtainMessage(UserHandler::transferToRouteOnHandler,
1369                         managerRecord.mUserRecord.mHandler,
1370                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1371     }
1372 
setSessionVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, int volume)1373     private void setSessionVolumeWithManagerLocked(int requestId,
1374             @NonNull IMediaRouter2Manager manager,
1375             @NonNull String uniqueSessionId, int volume) {
1376         final IBinder binder = manager.asBinder();
1377         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1378 
1379         if (managerRecord == null) {
1380             return;
1381         }
1382 
1383         Slog.i(TAG, TextUtils.formatSimple(
1384                 "setSessionVolumeWithManager | manager: %d, session: %s, volume: %d",
1385                 managerRecord.mManagerId, uniqueSessionId, volume));
1386 
1387         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1388         managerRecord.mUserRecord.mHandler.sendMessage(
1389                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1390                         managerRecord.mUserRecord.mHandler,
1391                         uniqueRequestId, uniqueSessionId, volume));
1392     }
1393 
releaseSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId)1394     private void releaseSessionWithManagerLocked(int requestId,
1395             @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId) {
1396         final IBinder binder = manager.asBinder();
1397         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1398 
1399         if (managerRecord == null) {
1400             return;
1401         }
1402 
1403         Slog.i(TAG, TextUtils.formatSimple(
1404                 "releaseSessionWithManager | manager: %d, session: %s",
1405                 managerRecord.mManagerId, uniqueSessionId));
1406 
1407         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1408                 .findRouterWithSessionLocked(uniqueSessionId);
1409 
1410         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1411         managerRecord.mUserRecord.mHandler.sendMessage(
1412                 obtainMessage(UserHandler::releaseSessionOnHandler,
1413                         managerRecord.mUserRecord.mHandler,
1414                         uniqueRequestId, routerRecord, uniqueSessionId));
1415     }
1416 
1417     // End of locked methods that are used by MediaRouter2Manager.
1418 
1419     // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
1420 
1421     @GuardedBy("mLock")
getOrCreateUserRecordLocked(int userId)1422     private UserRecord getOrCreateUserRecordLocked(int userId) {
1423         UserRecord userRecord = mUserRecords.get(userId);
1424         if (userRecord == null) {
1425             userRecord = new UserRecord(userId);
1426             mUserRecords.put(userId, userRecord);
1427             userRecord.init();
1428             if (isUserActiveLocked(userId)) {
1429                 userRecord.mHandler.sendMessage(
1430                         obtainMessage(UserHandler::start, userRecord.mHandler));
1431             }
1432         }
1433         return userRecord;
1434     }
1435 
1436     @GuardedBy("mLock")
disposeUserIfNeededLocked(@onNull UserRecord userRecord)1437     private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
1438         // If there are no records left and the user is no longer current then go ahead
1439         // and purge the user record and all of its associated state.  If the user is current
1440         // then leave it alone since we might be connected to a route or want to query
1441         // the same route information again soon.
1442         if (!isUserActiveLocked(userRecord.mUserId)
1443                 && userRecord.mRouterRecords.isEmpty()
1444                 && userRecord.mManagerRecords.isEmpty()) {
1445             if (DEBUG) {
1446                 Slog.d(TAG, userRecord + ": Disposed");
1447             }
1448             userRecord.mHandler.sendMessage(
1449                     obtainMessage(UserHandler::stop, userRecord.mHandler));
1450             mUserRecords.remove(userRecord.mUserId);
1451             // Note: User already stopped (by switchUser) so no need to send stop message here.
1452         }
1453     }
1454 
1455     // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
1456 
onDeviceConfigChange(@onNull DeviceConfig.Properties properties)1457     private void onDeviceConfigChange(@NonNull DeviceConfig.Properties properties) {
1458         sPackageImportanceForScanning = properties.getInt(
1459                 /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
1460                 /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
1461     }
1462 
toUniqueRequestId(int requesterId, int originalRequestId)1463     static long toUniqueRequestId(int requesterId, int originalRequestId) {
1464         return ((long) requesterId << 32) | originalRequestId;
1465     }
1466 
toRequesterId(long uniqueRequestId)1467     static int toRequesterId(long uniqueRequestId) {
1468         return (int) (uniqueRequestId >> 32);
1469     }
1470 
toOriginalRequestId(long uniqueRequestId)1471     static int toOriginalRequestId(long uniqueRequestId) {
1472         return (int) uniqueRequestId;
1473     }
1474 
1475     final class UserRecord {
1476         public final int mUserId;
1477         //TODO: make records private for thread-safety
1478         final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
1479         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
1480         RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
1481         Set<String> mActivelyScanningPackages = Set.of();
1482         final UserHandler mHandler;
1483 
UserRecord(int userId)1484         UserRecord(int userId) {
1485             mUserId = userId;
1486             mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
1487         }
1488 
init()1489         void init() {
1490             mHandler.init();
1491         }
1492 
1493         // TODO: This assumes that only one router exists in a package.
1494         //       Do this in Android S or later.
findRouterRecordLocked(String packageName)1495         RouterRecord findRouterRecordLocked(String packageName) {
1496             for (RouterRecord routerRecord : mRouterRecords) {
1497                 if (TextUtils.equals(routerRecord.mPackageName, packageName)) {
1498                     return routerRecord;
1499                 }
1500             }
1501             return null;
1502         }
1503 
dump(@onNull PrintWriter pw, @NonNull String prefix)1504         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
1505             pw.println(prefix + "UserRecord");
1506 
1507             String indent = prefix + "  ";
1508 
1509             pw.println(indent + "mUserId=" + mUserId);
1510 
1511             pw.println(indent + "Router Records:");
1512             if (!mRouterRecords.isEmpty()) {
1513                 for (RouterRecord routerRecord : mRouterRecords) {
1514                     routerRecord.dump(pw, indent + "  ");
1515                 }
1516             } else {
1517                 pw.println(indent + "<no router records>");
1518             }
1519 
1520             pw.println(indent + "Manager Records:");
1521             if (!mManagerRecords.isEmpty()) {
1522                 for (ManagerRecord managerRecord : mManagerRecords) {
1523                     managerRecord.dump(pw, indent + "  ");
1524                 }
1525             } else {
1526                 pw.println(indent + "<no manager records>");
1527             }
1528 
1529             pw.println(indent + "Composite discovery preference:");
1530             mCompositeDiscoveryPreference.dump(pw, indent + "  ");
1531             pw.println(
1532                     indent
1533                             + "Packages actively scanning: "
1534                             + String.join(", ", mActivelyScanningPackages));
1535 
1536             if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
1537                 pw.println(indent + "<could not dump handler state>");
1538             }
1539         }
1540     }
1541 
1542     final class RouterRecord implements IBinder.DeathRecipient {
1543         public final UserRecord mUserRecord;
1544         public final String mPackageName;
1545         public final List<Integer> mSelectRouteSequenceNumbers;
1546         public final IMediaRouter2 mRouter;
1547         public final int mUid;
1548         public final int mPid;
1549         public final boolean mHasConfigureWifiDisplayPermission;
1550         public final boolean mHasModifyAudioRoutingPermission;
1551         public final AtomicBoolean mHasBluetoothRoutingPermission;
1552         public final int mRouterId;
1553 
1554         public RouteDiscoveryPreference mDiscoveryPreference;
1555         @Nullable public RouteListingPreference mRouteListingPreference;
1556 
RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid, String packageName, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission)1557         RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid,
1558                 String packageName, boolean hasConfigureWifiDisplayPermission,
1559                 boolean hasModifyAudioRoutingPermission) {
1560             mUserRecord = userRecord;
1561             mPackageName = packageName;
1562             mSelectRouteSequenceNumbers = new ArrayList<>();
1563             mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
1564             mRouter = router;
1565             mUid = uid;
1566             mPid = pid;
1567             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
1568             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
1569             mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
1570             mRouterId = mNextRouterOrManagerId.getAndIncrement();
1571         }
1572 
fetchBluetoothPermission()1573         private boolean fetchBluetoothPermission() {
1574             boolean hasBluetoothRoutingPermission = true;
1575             for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
1576                 hasBluetoothRoutingPermission &=
1577                         mContext.checkPermission(permission, mPid, mUid)
1578                                 == PackageManager.PERMISSION_GRANTED;
1579             }
1580             return hasBluetoothRoutingPermission;
1581         }
1582 
1583         /**
1584          * Returns whether the corresponding router has permission to query and control system
1585          * routes.
1586          */
hasSystemRoutingPermission()1587         public boolean hasSystemRoutingPermission() {
1588             return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
1589         }
1590 
maybeUpdateSystemRoutingPermissionLocked()1591         public void maybeUpdateSystemRoutingPermissionLocked() {
1592             boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
1593             mHasBluetoothRoutingPermission.set(fetchBluetoothPermission());
1594             boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
1595             if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
1596                 Map<String, MediaRoute2Info> routesToReport =
1597                         newSystemRoutingPermissionValue
1598                                 ? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
1599                                 : mUserRecord.mHandler.mLastNotifiedRoutesToNonPrivilegedRouters;
1600                 notifyRoutesUpdated(routesToReport.values().stream().toList());
1601 
1602                 List<RoutingSessionInfo> sessionInfos =
1603                         mUserRecord.mHandler.mSystemProvider.getSessionInfos();
1604                 RoutingSessionInfo systemSessionToReport =
1605                         newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
1606                                 ? sessionInfos.get(0)
1607                                 : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
1608                 notifySessionInfoChanged(systemSessionToReport);
1609             }
1610         }
1611 
dispose()1612         public void dispose() {
1613             mRouter.asBinder().unlinkToDeath(this, 0);
1614         }
1615 
1616         @Override
binderDied()1617         public void binderDied() {
1618             routerDied(this);
1619         }
1620 
dump(@onNull PrintWriter pw, @NonNull String prefix)1621         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
1622             pw.println(prefix + "RouterRecord");
1623 
1624             String indent = prefix + "  ";
1625 
1626             pw.println(indent + "mPackageName=" + mPackageName);
1627             pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
1628             pw.println(indent + "mUid=" + mUid);
1629             pw.println(indent + "mPid=" + mPid);
1630             pw.println(indent + "mHasConfigureWifiDisplayPermission="
1631                     + mHasConfigureWifiDisplayPermission);
1632             pw.println(
1633                     indent
1634                             + "mHasModifyAudioRoutingPermission="
1635                             + mHasModifyAudioRoutingPermission);
1636             pw.println(
1637                     indent
1638                             + "mHasBluetoothRoutingPermission="
1639                             + mHasBluetoothRoutingPermission.get());
1640             pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
1641             pw.println(indent + "mRouterId=" + mRouterId);
1642 
1643             mDiscoveryPreference.dump(pw, indent);
1644         }
1645 
1646         /**
1647          * Notifies the corresponding router that it was successfully registered.
1648          *
1649          * <p>The message sent to the router includes a snapshot of the initial state, including
1650          * known routes and the system {@link RoutingSessionInfo}.
1651          *
1652          * @param currentRoutes All currently known routes, which are filtered according to package
1653          *     visibility before being sent to the router.
1654          * @param currentSystemSessionInfo The current system {@link RoutingSessionInfo}.
1655          */
notifyRegistered( List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo)1656         public void notifyRegistered(
1657                 List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) {
1658             try {
1659                 mRouter.notifyRouterRegistered(
1660                         getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
1661             } catch (RemoteException ex) {
1662                 Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
1663             }
1664         }
1665 
1666         /**
1667          * Sends the corresponding router an {@link
1668          * android.media.MediaRouter2.RouteCallback#onRoutesUpdated update} for the given {@code
1669          * routes}.
1670          *
1671          * <p>Only the routes that are visible to the router are sent as part of the update.
1672          */
notifyRoutesUpdated(List<MediaRoute2Info> routes)1673         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
1674             try {
1675                 mRouter.notifyRoutesUpdated(getVisibleRoutes(routes));
1676             } catch (RemoteException ex) {
1677                 Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
1678             }
1679         }
1680 
1681         /**
1682          * Sends the corresponding router an update for the given session.
1683          *
1684          * <p>Note: These updates are not directly visible to the app.
1685          */
notifySessionInfoChanged(RoutingSessionInfo sessionInfo)1686         public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
1687             try {
1688                 mRouter.notifySessionInfoChanged(sessionInfo);
1689             } catch (RemoteException ex) {
1690                 Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
1691             }
1692         }
1693 
1694         /**
1695          * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
1696          * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
1697          */
getVisibleRoutes(@onNull List<MediaRoute2Info> routes)1698         private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
1699             List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
1700             for (MediaRoute2Info route : routes) {
1701                 if (route.isVisibleTo(mPackageName)) {
1702                     filteredRoutes.add(route);
1703                 }
1704             }
1705             return filteredRoutes;
1706         }
1707     }
1708 
1709     final class ManagerRecord implements IBinder.DeathRecipient {
1710         public final UserRecord mUserRecord;
1711         public final IMediaRouter2Manager mManager;
1712         public final int mUid;
1713         public final int mPid;
1714         public final String mPackageName;
1715         public final int mManagerId;
1716         public SessionCreationRequest mLastSessionCreationRequest;
1717         public boolean mIsScanning;
1718 
ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager, int uid, int pid, String packageName)1719         ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager,
1720                 int uid, int pid, String packageName) {
1721             mUserRecord = userRecord;
1722             mManager = manager;
1723             mUid = uid;
1724             mPid = pid;
1725             mPackageName = packageName;
1726             mManagerId = mNextRouterOrManagerId.getAndIncrement();
1727         }
1728 
dispose()1729         public void dispose() {
1730             mManager.asBinder().unlinkToDeath(this, 0);
1731         }
1732 
1733         @Override
binderDied()1734         public void binderDied() {
1735             managerDied(this);
1736         }
1737 
dump(@onNull PrintWriter pw, @NonNull String prefix)1738         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
1739             pw.println(prefix + "ManagerRecord");
1740 
1741             String indent = prefix + "  ";
1742 
1743             pw.println(indent + "mPackageName=" + mPackageName);
1744             pw.println(indent + "mManagerId=" + mManagerId);
1745             pw.println(indent + "mUid=" + mUid);
1746             pw.println(indent + "mPid=" + mPid);
1747             pw.println(indent + "mIsScanning=" + mIsScanning);
1748 
1749             if (mLastSessionCreationRequest != null) {
1750                 mLastSessionCreationRequest.dump(pw, indent);
1751             }
1752         }
1753 
startScan()1754         public void startScan() {
1755             if (mIsScanning) {
1756                 return;
1757             }
1758             mIsScanning = true;
1759             mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
1760                     UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
1761         }
1762 
stopScan()1763         public void stopScan() {
1764             if (!mIsScanning) {
1765                 return;
1766             }
1767             mIsScanning = false;
1768             mUserRecord.mHandler.sendMessage(PooledLambda.obtainMessage(
1769                     UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
1770         }
1771 
1772         @Override
toString()1773         public String toString() {
1774             return "Manager " + mPackageName + " (pid " + mPid + ")";
1775         }
1776     }
1777 
1778     static final class UserHandler extends Handler implements
1779             MediaRoute2ProviderWatcher.Callback,
1780             MediaRoute2Provider.Callback {
1781 
1782         private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
1783         private final UserRecord mUserRecord;
1784         private final MediaRoute2ProviderWatcher mWatcher;
1785 
1786         private final SystemMediaRoute2Provider mSystemProvider;
1787         private final ArrayList<MediaRoute2Provider> mRouteProviders =
1788                 new ArrayList<>();
1789 
1790         private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
1791         private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
1792                 new CopyOnWriteArrayList<>();
1793         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
1794 
1795         /**
1796          * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
1797          * {@link android.media.MediaRouter2Manager managers}.
1798          *
1799          * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
1800          * that have {@code MODIFY_AUDIO_ROUTING} permission.
1801          *
1802          * <p>This list contains all routes exposed by route providers. This includes routes from
1803          * both system route providers and user route providers.
1804          *
1805          * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
1806          */
1807         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
1808                 new ArrayMap<>();
1809 
1810         /**
1811          * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
1812          *
1813          * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
1814          * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
1815          *
1816          * <p>This list contains all routes exposed by user route providers. It might also include
1817          * the current default route from {@link #mSystemProvider} to expose local route updates
1818          * (e.g. volume changes) to non-privileged routers.
1819          *
1820          * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
1821          */
1822         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
1823                 new ArrayMap<>();
1824 
1825         private boolean mRunning;
1826 
1827         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler(@onNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord)1828         UserHandler(@NonNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord) {
1829             super(Looper.getMainLooper(), null, true);
1830             mServiceRef = new WeakReference<>(service);
1831             mUserRecord = userRecord;
1832             mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
1833                     UserHandle.of(userRecord.mUserId));
1834             mRouteProviders.add(mSystemProvider);
1835             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
1836                     this, mUserRecord.mUserId);
1837         }
1838 
init()1839         void init() {
1840             mSystemProvider.setCallback(this);
1841         }
1842 
start()1843         private void start() {
1844             if (!mRunning) {
1845                 mRunning = true;
1846                 mSystemProvider.start();
1847                 mWatcher.start();
1848             }
1849         }
1850 
stop()1851         private void stop() {
1852             if (mRunning) {
1853                 mRunning = false;
1854                 mWatcher.stop(); // also stops all providers
1855                 mSystemProvider.stop();
1856             }
1857         }
1858 
1859         @Override
onAddProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)1860         public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
1861             proxy.setCallback(this);
1862             mRouteProviders.add(proxy);
1863             proxy.updateDiscoveryPreference(
1864                     mUserRecord.mActivelyScanningPackages,
1865                     mUserRecord.mCompositeDiscoveryPreference);
1866         }
1867 
1868         @Override
onRemoveProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)1869         public void onRemoveProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
1870             mRouteProviders.remove(proxy);
1871         }
1872 
1873         @Override
onProviderStateChanged(@onNull MediaRoute2Provider provider)1874         public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
1875             sendMessage(PooledLambda.obtainMessage(UserHandler::onProviderStateChangedOnHandler,
1876                     this, provider));
1877         }
1878 
1879         @Override
onSessionCreated(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)1880         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
1881                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
1882             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
1883                     this, provider, uniqueRequestId, sessionInfo));
1884         }
1885 
1886         @Override
onSessionUpdated(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1887         public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
1888                 @NonNull RoutingSessionInfo sessionInfo) {
1889             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
1890                     this, provider, sessionInfo));
1891         }
1892 
1893         @Override
onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)1894         public void onSessionReleased(@NonNull MediaRoute2Provider provider,
1895                 @NonNull RoutingSessionInfo sessionInfo) {
1896             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
1897                     this, provider, sessionInfo));
1898         }
1899 
1900         @Override
onRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)1901         public void onRequestFailed(@NonNull MediaRoute2Provider provider, long uniqueRequestId,
1902                 int reason) {
1903             sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
1904                     this, provider, uniqueRequestId, reason));
1905         }
1906 
1907         @Nullable
findRouterWithSessionLocked(@onNull String uniqueSessionId)1908         public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
1909             return mSessionToRouterMap.get(uniqueSessionId);
1910         }
1911 
1912         @Nullable
findManagerWithId(int managerId)1913         public ManagerRecord findManagerWithId(int managerId) {
1914             for (ManagerRecord manager : getManagerRecords()) {
1915                 if (manager.mManagerId == managerId) {
1916                     return manager;
1917                 }
1918             }
1919             return null;
1920         }
1921 
maybeUpdateDiscoveryPreferenceForUid(int uid)1922         public void maybeUpdateDiscoveryPreferenceForUid(int uid) {
1923             MediaRouter2ServiceImpl service = mServiceRef.get();
1924             if (service == null) {
1925                 return;
1926             }
1927             boolean isUidRelevant;
1928             synchronized (service.mLock) {
1929                 isUidRelevant = mUserRecord.mRouterRecords.stream().anyMatch(
1930                         router -> router.mUid == uid)
1931                         | mUserRecord.mManagerRecords.stream().anyMatch(
1932                             manager -> manager.mUid == uid);
1933             }
1934             if (isUidRelevant) {
1935                 sendMessage(PooledLambda.obtainMessage(
1936                         UserHandler::updateDiscoveryPreferenceOnHandler, this));
1937             }
1938         }
1939 
dump(@onNull PrintWriter pw, @NonNull String prefix)1940         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
1941             pw.println(prefix + "UserHandler");
1942 
1943             String indent = prefix + "  ";
1944             pw.println(indent + "mRunning=" + mRunning);
1945 
1946             mSystemProvider.dump(pw, prefix);
1947             mWatcher.dump(pw, prefix);
1948         }
1949 
onProviderStateChangedOnHandler(@onNull MediaRoute2Provider provider)1950         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
1951             MediaRoute2ProviderInfo newInfo = provider.getProviderInfo();
1952             int providerInfoIndex =
1953                     indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
1954             MediaRoute2ProviderInfo oldInfo =
1955                     providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
1956             MediaRouter2ServiceImpl mediaRouter2Service = mServiceRef.get();
1957 
1958             if (oldInfo == newInfo) {
1959                 // Nothing to do.
1960                 return;
1961             }
1962 
1963             Collection<MediaRoute2Info> newRoutes;
1964             Set<String> newRouteIds;
1965             if (newInfo != null) {
1966                 // Adding or updating a provider.
1967                 newRoutes = newInfo.getRoutes();
1968                 newRouteIds =
1969                         newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
1970                 if (providerInfoIndex >= 0) {
1971                     mLastProviderInfos.set(providerInfoIndex, newInfo);
1972                 } else {
1973                     mLastProviderInfos.add(newInfo);
1974                 }
1975             } else /* newInfo == null */ {
1976                 // Removing a provider.
1977                 mLastProviderInfos.remove(oldInfo);
1978                 newRouteIds = Collections.emptySet();
1979                 newRoutes = Collections.emptySet();
1980             }
1981 
1982             // Add new routes to the maps.
1983             ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
1984             boolean hasAddedOrModifiedRoutes = false;
1985             for (MediaRoute2Info newRouteInfo : newRoutes) {
1986                 if (!newRouteInfo.isValid()) {
1987                     Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
1988                             + newRouteInfo);
1989                     continue;
1990                 }
1991                 if (!provider.mIsSystemRouteProvider) {
1992                     mLastNotifiedRoutesToNonPrivilegedRouters.put(
1993                             newRouteInfo.getId(), newRouteInfo);
1994                 }
1995                 MediaRoute2Info oldRouteInfo =
1996                         mLastNotifiedRoutesToPrivilegedRouters.put(
1997                                 newRouteInfo.getId(), newRouteInfo);
1998                 hasAddedOrModifiedRoutes |= !newRouteInfo.equals(oldRouteInfo);
1999                 if (oldRouteInfo == null) {
2000                     addedRoutes.add(newRouteInfo);
2001                 }
2002             }
2003 
2004             // Remove stale routes from the maps.
2005             ArrayList<MediaRoute2Info> removedRoutes = new ArrayList<>();
2006             Collection<MediaRoute2Info> oldRoutes =
2007                     oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
2008             boolean hasRemovedRoutes = false;
2009             for (MediaRoute2Info oldRoute : oldRoutes) {
2010                 String oldRouteId = oldRoute.getId();
2011                 if (!newRouteIds.contains(oldRouteId)) {
2012                     hasRemovedRoutes = true;
2013                     mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
2014                     mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
2015                     removedRoutes.add(oldRoute);
2016                 }
2017             }
2018 
2019             if (!addedRoutes.isEmpty()) {
2020                 // If routes were added, newInfo cannot be null.
2021                 Slog.i(TAG,
2022                         toLoggingMessage(
2023                                 /* source= */ "addProviderRoutes",
2024                                 newInfo.getUniqueId(),
2025                                 addedRoutes));
2026             }
2027             if (!removedRoutes.isEmpty()) {
2028                 // If routes were removed, oldInfo cannot be null.
2029                 Slog.i(TAG,
2030                         toLoggingMessage(
2031                                 /* source= */ "removeProviderRoutes",
2032                                 oldInfo.getUniqueId(),
2033                                 removedRoutes));
2034             }
2035 
2036             dispatchUpdates(
2037                     hasAddedOrModifiedRoutes,
2038                     hasRemovedRoutes,
2039                     provider.mIsSystemRouteProvider,
2040                     mSystemProvider.getDefaultRoute());
2041         }
2042 
toLoggingMessage( String source, String providerId, ArrayList<MediaRoute2Info> routes)2043         private static String toLoggingMessage(
2044                 String source, String providerId, ArrayList<MediaRoute2Info> routes) {
2045             String routesString =
2046                     routes.stream()
2047                             .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
2048                             .collect(Collectors.joining(/* delimiter= */ ", "));
2049             return TextUtils.formatSimple("%s | provider: %s, routes: [%s]",
2050                     source, providerId, routesString);
2051         }
2052 
2053         /**
2054          * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
2055          * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
2056          * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
2057          * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
2058          * were made.
2059          *
2060          * @param hasAddedOrModifiedRoutes whether routes were added or modified.
2061          * @param hasRemovedRoutes whether routes were removed.
2062          * @param isSystemProvider whether the latest update was caused by a system provider.
2063          * @param defaultRoute the current default route in {@link #mSystemProvider}.
2064          */
dispatchUpdates( boolean hasAddedOrModifiedRoutes, boolean hasRemovedRoutes, boolean isSystemProvider, MediaRoute2Info defaultRoute)2065         private void dispatchUpdates(
2066                 boolean hasAddedOrModifiedRoutes,
2067                 boolean hasRemovedRoutes,
2068                 boolean isSystemProvider,
2069                 MediaRoute2Info defaultRoute) {
2070 
2071             // Ignore if no changes.
2072             if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
2073                 return;
2074             }
2075             List<RouterRecord> routerRecordsWithModifyAudioRoutingPermission =
2076                     getRouterRecords(true);
2077             List<RouterRecord> routerRecordsWithoutModifyAudioRoutingPermission =
2078                     getRouterRecords(false);
2079             List<IMediaRouter2Manager> managers = getManagers();
2080 
2081             // Managers receive all provider updates with all routes.
2082             notifyRoutesUpdatedToManagers(
2083                     managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
2084 
2085             // Routers with modify audio permission (usually system routers) receive all provider
2086             // updates with all routes.
2087             notifyRoutesUpdatedToRouterRecords(
2088                     routerRecordsWithModifyAudioRoutingPermission,
2089                     new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
2090 
2091             if (!isSystemProvider) {
2092                 // Regular routers receive updates from all non-system providers with all non-system
2093                 // routes.
2094                 notifyRoutesUpdatedToRouterRecords(
2095                         routerRecordsWithoutModifyAudioRoutingPermission,
2096                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
2097             } else if (hasAddedOrModifiedRoutes) {
2098                 // On system provider updates, regular routers receive the updated default route.
2099                 // This is the only system route they should receive.
2100                 mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
2101                 notifyRoutesUpdatedToRouterRecords(
2102                         routerRecordsWithoutModifyAudioRoutingPermission,
2103                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
2104             }
2105         }
2106 
2107         /**
2108          * Returns the index of the first element in {@code lastProviderInfos} that matches the
2109          * specified unique id.
2110          *
2111          * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
2112          * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
2113          * @return index of found element, or -1 if not found.
2114          */
indexOfRouteProviderInfoByUniqueId( @onNull String uniqueId, @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos)2115         private static int indexOfRouteProviderInfoByUniqueId(
2116                 @NonNull String uniqueId,
2117                 @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
2118             for (int i = 0; i < lastProviderInfos.size(); i++) {
2119                 MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
2120                 if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
2121                     return i;
2122                 }
2123             }
2124             return -1;
2125         }
2126 
requestRouterCreateSessionOnHandler(long uniqueRequestId, @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)2127         private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
2128                 @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
2129                 @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
2130             try {
2131                 if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
2132                     // The router lacks permission to modify system routing, so we hide system
2133                     // route info from them.
2134                     route = mSystemProvider.getDefaultRoute();
2135                 }
2136                 routerRecord.mRouter.requestCreateSessionByManager(
2137                         uniqueRequestId, oldSession, route);
2138             } catch (RemoteException ex) {
2139                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
2140                         + "Failed to request. Router probably died.", ex);
2141                 notifyRequestFailedToManager(managerRecord.mManager,
2142                         toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
2143             }
2144         }
2145 
requestCreateSessionWithRouter2OnHandler(long uniqueRequestId, long managerRequestId, @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)2146         private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
2147                 long managerRequestId, @NonNull RouterRecord routerRecord,
2148                 @NonNull RoutingSessionInfo oldSession,
2149                 @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
2150 
2151             final MediaRoute2Provider provider = findProvider(route.getProviderId());
2152             if (provider == null) {
2153                 Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session "
2154                         + "creation request since no provider found for given route=" + route);
2155                 notifySessionCreationFailedToRouter(routerRecord,
2156                         toOriginalRequestId(uniqueRequestId));
2157                 return;
2158             }
2159 
2160             SessionCreationRequest request =
2161                     new SessionCreationRequest(routerRecord, uniqueRequestId,
2162                             managerRequestId, oldSession, route);
2163             mSessionCreationRequests.add(request);
2164 
2165             provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
2166                     route.getOriginalId(), sessionHints);
2167         }
2168 
2169         // routerRecord can be null if the session is system's or RCN.
selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)2170         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
2171                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
2172             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2173                     "selecting")) {
2174                 return;
2175             }
2176 
2177             final String providerId = route.getProviderId();
2178             final MediaRoute2Provider provider = findProvider(providerId);
2179             if (provider == null) {
2180                 return;
2181             }
2182             provider.selectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
2183                     route.getOriginalId());
2184         }
2185 
2186         // routerRecord can be null if the session is system's or RCN.
deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)2187         private void deselectRouteOnHandler(long uniqueRequestId,
2188                 @Nullable RouterRecord routerRecord,
2189                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
2190             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2191                     "deselecting")) {
2192                 return;
2193             }
2194 
2195             final String providerId = route.getProviderId();
2196             final MediaRoute2Provider provider = findProvider(providerId);
2197             if (provider == null) {
2198                 return;
2199             }
2200 
2201             provider.deselectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
2202                     route.getOriginalId());
2203         }
2204 
2205         // routerRecord can be null if the session is system's or RCN.
transferToRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)2206         private void transferToRouteOnHandler(long uniqueRequestId,
2207                 @Nullable RouterRecord routerRecord,
2208                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
2209             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
2210                     "transferring to")) {
2211                 return;
2212             }
2213 
2214             final String providerId = route.getProviderId();
2215             final MediaRoute2Provider provider = findProvider(providerId);
2216             if (provider == null) {
2217                 return;
2218             }
2219             provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
2220                     route.getOriginalId());
2221         }
2222 
2223         // routerRecord is null if and only if the session is created without the request, which
2224         // includes the system's session and RCN cases.
checkArgumentsForSessionControl(@ullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description)2225         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
2226                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
2227                 @NonNull String description) {
2228             final String providerId = route.getProviderId();
2229             final MediaRoute2Provider provider = findProvider(providerId);
2230             if (provider == null) {
2231                 Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
2232                         + "given route=" + route);
2233                 return false;
2234             }
2235 
2236             // Bypass checking router if it's the system session (routerRecord should be null)
2237             if (TextUtils.equals(getProviderId(uniqueSessionId), mSystemProvider.getUniqueId())) {
2238                 return true;
2239             }
2240 
2241             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
2242             if (matchingRecord != routerRecord) {
2243                 Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
2244                         + "packageName=" + routerRecord.mPackageName + " route=" + route);
2245                 return false;
2246             }
2247 
2248             final String sessionId = getOriginalId(uniqueSessionId);
2249             if (sessionId == null) {
2250                 Slog.w(TAG, "Failed to get original session id from unique session id. "
2251                         + "uniqueSessionId=" + uniqueSessionId);
2252                 return false;
2253             }
2254 
2255             return true;
2256         }
2257 
setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route, int volume)2258         private void setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route,
2259                 int volume) {
2260             final MediaRoute2Provider provider = findProvider(route.getProviderId());
2261             if (provider == null) {
2262                 Slog.w(TAG, "setRouteVolumeOnHandler: Couldn't find provider for route=" + route);
2263                 return;
2264             }
2265             provider.setRouteVolume(uniqueRequestId, route.getOriginalId(), volume);
2266         }
2267 
setSessionVolumeOnHandler(long uniqueRequestId, @NonNull String uniqueSessionId, int volume)2268         private void setSessionVolumeOnHandler(long uniqueRequestId,
2269                 @NonNull String uniqueSessionId, int volume) {
2270             final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
2271             if (provider == null) {
2272                 Slog.w(TAG, "setSessionVolumeOnHandler: Couldn't find provider for session id="
2273                         + uniqueSessionId);
2274                 return;
2275             }
2276             provider.setSessionVolume(uniqueRequestId, getOriginalId(uniqueSessionId), volume);
2277         }
2278 
releaseSessionOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId)2279         private void releaseSessionOnHandler(long uniqueRequestId,
2280                 @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
2281             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
2282             if (matchingRecord != routerRecord) {
2283                 Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName="
2284                         + (routerRecord == null ? null : routerRecord.mPackageName)
2285                         + " uniqueSessionId=" + uniqueSessionId);
2286                 return;
2287             }
2288 
2289             final String providerId = getProviderId(uniqueSessionId);
2290             if (providerId == null) {
2291                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
2292                         + "uniqueSessionId=" + uniqueSessionId);
2293                 return;
2294             }
2295 
2296             final String sessionId = getOriginalId(uniqueSessionId);
2297             if (sessionId == null) {
2298                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
2299                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
2300                 return;
2301             }
2302 
2303             final MediaRoute2Provider provider = findProvider(providerId);
2304             if (provider == null) {
2305                 Slog.w(TAG, "Ignoring releasing session since no provider found for given "
2306                         + "providerId=" + providerId);
2307                 return;
2308             }
2309 
2310             provider.releaseSession(uniqueRequestId, sessionId);
2311         }
2312 
onSessionCreatedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)2313         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
2314                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
2315             SessionCreationRequest matchingRequest = null;
2316 
2317             for (SessionCreationRequest request : mSessionCreationRequests) {
2318                 if (request.mUniqueRequestId == uniqueRequestId
2319                         && TextUtils.equals(
2320                         request.mRoute.getProviderId(), provider.getUniqueId())) {
2321                     matchingRequest = request;
2322                     break;
2323                 }
2324             }
2325 
2326             long managerRequestId = (matchingRequest == null)
2327                     ? MediaRoute2ProviderService.REQUEST_ID_NONE
2328                     : matchingRequest.mManagerRequestId;
2329             notifySessionCreatedToManagers(managerRequestId, sessionInfo);
2330 
2331             if (matchingRequest == null) {
2332                 Slog.w(TAG, "Ignoring session creation result for unknown request. "
2333                         + "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
2334                 return;
2335             }
2336 
2337             mSessionCreationRequests.remove(matchingRequest);
2338             // Not to show old session
2339             MediaRoute2Provider oldProvider =
2340                     findProvider(matchingRequest.mOldSession.getProviderId());
2341             if (oldProvider != null) {
2342                 oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
2343             } else {
2344                 Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
2345                         + "session=" + matchingRequest.mOldSession);
2346             }
2347 
2348             mSessionToRouterMap.put(sessionInfo.getId(), matchingRequest.mRouterRecord);
2349             if (sessionInfo.isSystemSession()
2350                     && !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
2351                 // The router lacks permission to modify system routing, so we hide system routing
2352                 // session info from them.
2353                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
2354             }
2355             notifySessionCreatedToRouter(
2356                     matchingRequest.mRouterRecord,
2357                     toOriginalRequestId(uniqueRequestId),
2358                     sessionInfo);
2359         }
2360 
onSessionInfoChangedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)2361         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
2362                 @NonNull RoutingSessionInfo sessionInfo) {
2363             List<IMediaRouter2Manager> managers = getManagers();
2364             notifySessionUpdatedToManagers(managers, sessionInfo);
2365 
2366             // For system provider, notify all routers.
2367             if (provider == mSystemProvider) {
2368                 if (mServiceRef.get() == null) {
2369                     return;
2370                 }
2371                 notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
2372                 notifySessionInfoChangedToRouters(
2373                         getRouterRecords(false), mSystemProvider.getDefaultSessionInfo());
2374                 return;
2375             }
2376 
2377             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
2378             if (routerRecord == null) {
2379                 Slog.w(TAG, "onSessionInfoChangedOnHandler: No matching router found for session="
2380                         + sessionInfo);
2381                 return;
2382             }
2383             notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
2384         }
2385 
onSessionReleasedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)2386         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
2387                 @NonNull RoutingSessionInfo sessionInfo) {
2388             List<IMediaRouter2Manager> managers = getManagers();
2389             notifySessionReleasedToManagers(managers, sessionInfo);
2390 
2391             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
2392             if (routerRecord == null) {
2393                 Slog.w(TAG, "onSessionReleasedOnHandler: No matching router found for session="
2394                         + sessionInfo);
2395                 return;
2396             }
2397             notifySessionReleasedToRouter(routerRecord, sessionInfo);
2398         }
2399 
onRequestFailedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)2400         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
2401                 long uniqueRequestId, int reason) {
2402             if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) {
2403                 return;
2404             }
2405 
2406             final int requesterId = toRequesterId(uniqueRequestId);
2407             ManagerRecord manager = findManagerWithId(requesterId);
2408             if (manager != null) {
2409                 notifyRequestFailedToManager(
2410                         manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
2411                 return;
2412             }
2413 
2414             // Currently, only the manager can get notified of failures.
2415             // TODO: Notify router too when the related callback is introduced.
2416         }
2417 
handleSessionCreationRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)2418         private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
2419                 long uniqueRequestId, int reason) {
2420             // Check whether the failure is about creating a session
2421             SessionCreationRequest matchingRequest = null;
2422             for (SessionCreationRequest request : mSessionCreationRequests) {
2423                 if (request.mUniqueRequestId == uniqueRequestId && TextUtils.equals(
2424                         request.mRoute.getProviderId(), provider.getUniqueId())) {
2425                     matchingRequest = request;
2426                     break;
2427                 }
2428             }
2429 
2430             if (matchingRequest == null) {
2431                 // The failure is not about creating a session.
2432                 return false;
2433             }
2434 
2435             mSessionCreationRequests.remove(matchingRequest);
2436 
2437             // Notify the requester about the failure.
2438             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
2439             if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
2440                 notifySessionCreationFailedToRouter(
2441                         matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
2442             } else {
2443                 final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
2444                 ManagerRecord manager = findManagerWithId(requesterId);
2445                 if (manager != null) {
2446                     notifyRequestFailedToManager(manager.mManager,
2447                             toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
2448                 }
2449             }
2450             return true;
2451         }
2452 
notifySessionCreatedToRouter(@onNull RouterRecord routerRecord, int requestId, @NonNull RoutingSessionInfo sessionInfo)2453         private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
2454                 int requestId, @NonNull RoutingSessionInfo sessionInfo) {
2455             try {
2456                 routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
2457             } catch (RemoteException ex) {
2458                 Slog.w(TAG, "Failed to notify router of the session creation."
2459                         + " Router probably died.", ex);
2460             }
2461         }
2462 
notifySessionCreationFailedToRouter(@onNull RouterRecord routerRecord, int requestId)2463         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
2464                 int requestId) {
2465             try {
2466                 routerRecord.mRouter.notifySessionCreated(requestId,
2467                         /* sessionInfo= */ null);
2468             } catch (RemoteException ex) {
2469                 Slog.w(TAG, "Failed to notify router of the session creation failure."
2470                         + " Router probably died.", ex);
2471             }
2472         }
2473 
notifySessionReleasedToRouter(@onNull RouterRecord routerRecord, @NonNull RoutingSessionInfo sessionInfo)2474         private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
2475                 @NonNull RoutingSessionInfo sessionInfo) {
2476             try {
2477                 routerRecord.mRouter.notifySessionReleased(sessionInfo);
2478             } catch (RemoteException ex) {
2479                 Slog.w(TAG, "Failed to notify router of the session release."
2480                         + " Router probably died.", ex);
2481             }
2482         }
2483 
getManagers()2484         private List<IMediaRouter2Manager> getManagers() {
2485             final List<IMediaRouter2Manager> managers = new ArrayList<>();
2486             MediaRouter2ServiceImpl service = mServiceRef.get();
2487             if (service == null) {
2488                 return managers;
2489             }
2490             synchronized (service.mLock) {
2491                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
2492                     managers.add(managerRecord.mManager);
2493                 }
2494             }
2495             return managers;
2496         }
2497 
getRouterRecords()2498         private List<RouterRecord> getRouterRecords() {
2499             MediaRouter2ServiceImpl service = mServiceRef.get();
2500             if (service == null) {
2501                 return Collections.emptyList();
2502             }
2503             synchronized (service.mLock) {
2504                 return new ArrayList<>(mUserRecord.mRouterRecords);
2505             }
2506         }
2507 
getRouterRecords(boolean hasModifyAudioRoutingPermission)2508         private List<RouterRecord> getRouterRecords(boolean hasModifyAudioRoutingPermission) {
2509             MediaRouter2ServiceImpl service = mServiceRef.get();
2510             List<RouterRecord> routerRecords = new ArrayList<>();
2511             if (service == null) {
2512                 return routerRecords;
2513             }
2514             synchronized (service.mLock) {
2515                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
2516                     if (hasModifyAudioRoutingPermission
2517                             == routerRecord.hasSystemRoutingPermission()) {
2518                         routerRecords.add(routerRecord);
2519                     }
2520                 }
2521                 return routerRecords;
2522             }
2523         }
2524 
getManagerRecords()2525         private List<ManagerRecord> getManagerRecords() {
2526             MediaRouter2ServiceImpl service = mServiceRef.get();
2527             if (service == null) {
2528                 return Collections.emptyList();
2529             }
2530             synchronized (service.mLock) {
2531                 return new ArrayList<>(mUserRecord.mManagerRecords);
2532             }
2533         }
2534 
notifyRouterRegistered(@onNull RouterRecord routerRecord)2535         private void notifyRouterRegistered(@NonNull RouterRecord routerRecord) {
2536             List<MediaRoute2Info> currentRoutes = new ArrayList<>();
2537 
2538             MediaRoute2ProviderInfo systemProviderInfo = null;
2539             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
2540                 // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
2541                 if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
2542                     // Adding routes from system provider will be handled below, so skip it here.
2543                     systemProviderInfo = providerInfo;
2544                     continue;
2545                 }
2546                 currentRoutes.addAll(providerInfo.getRoutes());
2547             }
2548 
2549             RoutingSessionInfo currentSystemSessionInfo;
2550             if (routerRecord.hasSystemRoutingPermission()) {
2551                 if (systemProviderInfo != null) {
2552                     currentRoutes.addAll(systemProviderInfo.getRoutes());
2553                 } else {
2554                     // This shouldn't happen.
2555                     Slog.wtf(TAG, "System route provider not found.");
2556                 }
2557                 currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0);
2558             } else {
2559                 currentRoutes.add(mSystemProvider.getDefaultRoute());
2560                 currentSystemSessionInfo = mSystemProvider.getDefaultSessionInfo();
2561             }
2562 
2563             if (currentRoutes.size() == 0) {
2564                 return;
2565             }
2566 
2567             routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
2568         }
2569 
notifyRoutesUpdatedToRouterRecords( @onNull List<RouterRecord> routerRecords, @NonNull List<MediaRoute2Info> routes)2570         private static void notifyRoutesUpdatedToRouterRecords(
2571                 @NonNull List<RouterRecord> routerRecords,
2572                 @NonNull List<MediaRoute2Info> routes) {
2573             for (RouterRecord routerRecord : routerRecords) {
2574                 routerRecord.notifyRoutesUpdated(routes);
2575             }
2576         }
2577 
notifySessionInfoChangedToRouters( @onNull List<RouterRecord> routerRecords, @NonNull RoutingSessionInfo sessionInfo)2578         private void notifySessionInfoChangedToRouters(
2579                 @NonNull List<RouterRecord> routerRecords,
2580                 @NonNull RoutingSessionInfo sessionInfo) {
2581             for (RouterRecord routerRecord : routerRecords) {
2582                 routerRecord.notifySessionInfoChanged(sessionInfo);
2583             }
2584         }
2585 
2586         /**
2587          * Notifies {@code manager} with all known routes. This only happens once after {@code
2588          * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
2589          * registerManager()}.
2590          *
2591          * @param manager {@link IMediaRouter2Manager} to be notified.
2592          */
notifyInitialRoutesToManager(@onNull IMediaRouter2Manager manager)2593         private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
2594             if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
2595                 return;
2596             }
2597             try {
2598                 manager.notifyRoutesUpdated(
2599                         new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
2600             } catch (RemoteException ex) {
2601                 Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
2602             }
2603         }
2604 
notifyRoutesUpdatedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull List<MediaRoute2Info> routes)2605         private void notifyRoutesUpdatedToManagers(
2606                 @NonNull List<IMediaRouter2Manager> managers,
2607                 @NonNull List<MediaRoute2Info> routes) {
2608             for (IMediaRouter2Manager manager : managers) {
2609                 try {
2610                     manager.notifyRoutesUpdated(routes);
2611                 } catch (RemoteException ex) {
2612                     Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
2613                 }
2614             }
2615         }
2616 
notifySessionCreatedToManagers(long managerRequestId, @NonNull RoutingSessionInfo session)2617         private void notifySessionCreatedToManagers(long managerRequestId,
2618                 @NonNull RoutingSessionInfo session) {
2619             int requesterId = toRequesterId(managerRequestId);
2620             int originalRequestId = toOriginalRequestId(managerRequestId);
2621 
2622             for (ManagerRecord manager : getManagerRecords()) {
2623                 try {
2624                     manager.mManager.notifySessionCreated(
2625                             ((manager.mManagerId == requesterId) ? originalRequestId :
2626                                     MediaRouter2Manager.REQUEST_ID_NONE), session);
2627                 } catch (RemoteException ex) {
2628                     Slog.w(TAG, "notifySessionCreatedToManagers: "
2629                             + "Failed to notify. Manager probably died.", ex);
2630                 }
2631             }
2632         }
2633 
notifySessionUpdatedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)2634         private void notifySessionUpdatedToManagers(
2635                 @NonNull List<IMediaRouter2Manager> managers,
2636                 @NonNull RoutingSessionInfo sessionInfo) {
2637             for (IMediaRouter2Manager manager : managers) {
2638                 try {
2639                     manager.notifySessionUpdated(sessionInfo);
2640                 } catch (RemoteException ex) {
2641                     Slog.w(TAG, "notifySessionUpdatedToManagers: "
2642                             + "Failed to notify. Manager probably died.", ex);
2643                 }
2644             }
2645         }
2646 
notifySessionReleasedToManagers( @onNull List<IMediaRouter2Manager> managers, @NonNull RoutingSessionInfo sessionInfo)2647         private void notifySessionReleasedToManagers(
2648                 @NonNull List<IMediaRouter2Manager> managers,
2649                 @NonNull RoutingSessionInfo sessionInfo) {
2650             for (IMediaRouter2Manager manager : managers) {
2651                 try {
2652                     manager.notifySessionReleased(sessionInfo);
2653                 } catch (RemoteException ex) {
2654                     Slog.w(TAG, "notifySessionReleasedToManagers: "
2655                             + "Failed to notify. Manager probably died.", ex);
2656                 }
2657             }
2658         }
2659 
notifyDiscoveryPreferenceChangedToManager(@onNull RouterRecord routerRecord, @NonNull IMediaRouter2Manager manager)2660         private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
2661                 @NonNull IMediaRouter2Manager manager) {
2662             try {
2663                 manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
2664                         routerRecord.mDiscoveryPreference);
2665             } catch (RemoteException ex) {
2666                 Slog.w(TAG, "Failed to notify preferred features changed."
2667                         + " Manager probably died.", ex);
2668             }
2669         }
2670 
notifyDiscoveryPreferenceChangedToManagers(@onNull String routerPackageName, @Nullable RouteDiscoveryPreference discoveryPreference)2671         private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
2672                 @Nullable RouteDiscoveryPreference discoveryPreference) {
2673             MediaRouter2ServiceImpl service = mServiceRef.get();
2674             if (service == null) {
2675                 return;
2676             }
2677             List<IMediaRouter2Manager> managers = new ArrayList<>();
2678             synchronized (service.mLock) {
2679                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
2680                     managers.add(managerRecord.mManager);
2681                 }
2682             }
2683             for (IMediaRouter2Manager manager : managers) {
2684                 try {
2685                     manager.notifyDiscoveryPreferenceChanged(routerPackageName,
2686                             discoveryPreference);
2687                 } catch (RemoteException ex) {
2688                     Slog.w(TAG, "Failed to notify preferred features changed."
2689                             + " Manager probably died.", ex);
2690                 }
2691             }
2692         }
2693 
notifyRouteListingPreferenceChangeToManagers( String routerPackageName, @Nullable RouteListingPreference routeListingPreference)2694         private void notifyRouteListingPreferenceChangeToManagers(
2695                 String routerPackageName, @Nullable RouteListingPreference routeListingPreference) {
2696             MediaRouter2ServiceImpl service = mServiceRef.get();
2697             if (service == null) {
2698                 return;
2699             }
2700             List<IMediaRouter2Manager> managers = new ArrayList<>();
2701             synchronized (service.mLock) {
2702                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
2703                     managers.add(managerRecord.mManager);
2704                 }
2705             }
2706             for (IMediaRouter2Manager manager : managers) {
2707                 try {
2708                     manager.notifyRouteListingPreferenceChange(
2709                             routerPackageName, routeListingPreference);
2710                 } catch (RemoteException ex) {
2711                     Slog.w(
2712                             TAG,
2713                             "Failed to notify preferred features changed."
2714                                     + " Manager probably died.",
2715                             ex);
2716                 }
2717             }
2718             // TODO(b/238178508): In order to support privileged media router instances, we also
2719             //    need to update routers other than the one making the update.
2720         }
2721 
notifyRequestFailedToManager(@onNull IMediaRouter2Manager manager, int requestId, int reason)2722         private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
2723                 int requestId, int reason) {
2724             try {
2725                 manager.notifyRequestFailed(requestId, reason);
2726             } catch (RemoteException ex) {
2727                 Slog.w(TAG, "Failed to notify manager of the request failure."
2728                         + " Manager probably died.", ex);
2729             }
2730         }
2731 
updateDiscoveryPreferenceOnHandler()2732         private void updateDiscoveryPreferenceOnHandler() {
2733             MediaRouter2ServiceImpl service = mServiceRef.get();
2734             if (service == null) {
2735                 return;
2736             }
2737             List<RouterRecord> activeRouterRecords = Collections.emptyList();
2738             List<RouterRecord> allRouterRecords = getRouterRecords();
2739             List<ManagerRecord> managerRecords = getManagerRecords();
2740 
2741             boolean isManagerScanning = false;
2742             if (service.mPowerManager.isInteractive()) {
2743                 isManagerScanning = managerRecords.stream().anyMatch(manager ->
2744                         manager.mIsScanning && service.mActivityManager
2745                                 .getPackageImportance(manager.mPackageName)
2746                                 <= sPackageImportanceForScanning);
2747 
2748                 if (isManagerScanning) {
2749                     activeRouterRecords = allRouterRecords;
2750                 } else {
2751                     activeRouterRecords =
2752                             allRouterRecords.stream()
2753                                     .filter(
2754                                             record ->
2755                                                     service.mActivityManager.getPackageImportance(
2756                                                                     record.mPackageName)
2757                                                             <= sPackageImportanceForScanning)
2758                                     .collect(Collectors.toList());
2759                 }
2760             }
2761 
2762             for (MediaRoute2Provider provider : mRouteProviders) {
2763                 if (provider instanceof MediaRoute2ProviderServiceProxy) {
2764                     ((MediaRoute2ProviderServiceProxy) provider)
2765                             .setManagerScanning(isManagerScanning);
2766                 }
2767             }
2768 
2769             // Build a composite RouteDiscoveryPreference that matches all of the routes
2770             // that match one or more of the individual discovery preferences. It may also
2771             // match additional routes. The composite RouteDiscoveryPreference can be used
2772             // to query route providers once to obtain all of the routes of interest, which
2773             // can be subsequently filtered for the individual discovery preferences.
2774             Set<String> preferredFeatures = new HashSet<>();
2775             Set<String> activelyScanningPackages = new HashSet<>();
2776             boolean activeScan = false;
2777             for (RouterRecord activeRouterRecord : activeRouterRecords) {
2778                 RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
2779                 preferredFeatures.addAll(preference.getPreferredFeatures());
2780                 if (preference.shouldPerformActiveScan()) {
2781                     activeScan = true;
2782                     activelyScanningPackages.add(activeRouterRecord.mPackageName);
2783                 }
2784             }
2785             RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
2786                     List.copyOf(preferredFeatures), activeScan || isManagerScanning).build();
2787 
2788             synchronized (service.mLock) {
2789                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
2790                         && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
2791                     return;
2792                 }
2793                 mUserRecord.mCompositeDiscoveryPreference = newPreference;
2794                 mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
2795             }
2796             for (MediaRoute2Provider provider : mRouteProviders) {
2797                 provider.updateDiscoveryPreference(
2798                         activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
2799             }
2800         }
2801 
findProvider(@ullable String providerId)2802         private MediaRoute2Provider findProvider(@Nullable String providerId) {
2803             for (MediaRoute2Provider provider : mRouteProviders) {
2804                 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
2805                     return provider;
2806                 }
2807             }
2808             return null;
2809         }
2810     }
2811     static final class SessionCreationRequest {
2812         public final RouterRecord mRouterRecord;
2813         public final long mUniqueRequestId;
2814         public final long mManagerRequestId;
2815         public final RoutingSessionInfo mOldSession;
2816         public final MediaRoute2Info mRoute;
2817 
SessionCreationRequest(@onNull RouterRecord routerRecord, long uniqueRequestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)2818         SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
2819                 long managerRequestId, @NonNull RoutingSessionInfo oldSession,
2820                 @NonNull MediaRoute2Info route) {
2821             mRouterRecord = routerRecord;
2822             mUniqueRequestId = uniqueRequestId;
2823             mManagerRequestId = managerRequestId;
2824             mOldSession = oldSession;
2825             mRoute = route;
2826         }
2827 
dump(@onNull PrintWriter pw, @NonNull String prefix)2828         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2829             pw.println(prefix + "SessionCreationRequest");
2830 
2831             String indent = prefix + "  ";
2832 
2833             pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
2834             pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
2835             mOldSession.dump(pw, indent);
2836             mRoute.dump(pw, prefix);
2837         }
2838     }
2839 }
2840