• 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;
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.MediaRouter2.SCANNING_STATE_NOT_SCANNING;
24 import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL;
25 import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
26 import static android.media.MediaRouter2Utils.getOriginalId;
27 import static android.media.MediaRouter2Utils.getProviderId;
28 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
29 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
30 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE;
31 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION;
32 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE;
33 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE;
34 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
35 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID;
36 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID;
37 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND;
38 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED;
39 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND;
40 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
41 import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
42 
43 import android.Manifest;
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.RequiresPermission;
47 import android.app.ActivityManager;
48 import android.app.AppOpsManager;
49 import android.content.BroadcastReceiver;
50 import android.content.ComponentName;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.content.IntentFilter;
54 import android.content.PermissionChecker;
55 import android.content.pm.PackageManager;
56 import android.media.IMediaRouter2;
57 import android.media.IMediaRouter2Manager;
58 import android.media.MediaRoute2Info;
59 import android.media.MediaRoute2ProviderInfo;
60 import android.media.MediaRoute2ProviderService;
61 import android.media.MediaRouter2.ScanningState;
62 import android.media.MediaRouter2Manager;
63 import android.media.RouteDiscoveryPreference;
64 import android.media.RouteListingPreference;
65 import android.media.RoutingSessionInfo;
66 import android.media.SuggestedDeviceInfo;
67 import android.os.Binder;
68 import android.os.Bundle;
69 import android.os.Handler;
70 import android.os.IBinder;
71 import android.os.Looper;
72 import android.os.PowerManager;
73 import android.os.RemoteException;
74 import android.os.UserHandle;
75 import android.text.TextUtils;
76 import android.util.ArrayMap;
77 import android.util.Log;
78 import android.util.Slog;
79 import android.util.SparseArray;
80 import com.android.internal.annotations.GuardedBy;
81 import com.android.internal.util.function.pooled.PooledLambda;
82 import com.android.media.flags.Flags;
83 import com.android.server.LocalServices;
84 import com.android.server.pm.UserManagerInternal;
85 import com.android.server.statusbar.StatusBarManagerInternal;
86 import java.io.PrintWriter;
87 import java.lang.ref.WeakReference;
88 import java.util.ArrayList;
89 import java.util.Arrays;
90 import java.util.Collection;
91 import java.util.Collections;
92 import java.util.HashMap;
93 import java.util.HashSet;
94 import java.util.List;
95 import java.util.Map;
96 import java.util.Objects;
97 import java.util.Optional;
98 import java.util.Set;
99 import java.util.concurrent.CopyOnWriteArrayList;
100 import java.util.concurrent.atomic.AtomicBoolean;
101 import java.util.concurrent.atomic.AtomicInteger;
102 import java.util.stream.Collectors;
103 
104 /**
105  * Implements features related to {@link android.media.MediaRouter2} and
106  * {@link android.media.MediaRouter2Manager}.
107  */
108 class MediaRouter2ServiceImpl {
109     private static final String TAG = "MR2ServiceImpl";
110     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
111 
112     // TODO: (In Android S or later) if we add callback methods for generic failures
113     //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
114     private static final long DUMMY_REQUEST_ID = -1;
115 
116     private static final int REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING = IMPORTANCE_FOREGROUND;
117 
118     /**
119      * Contains the list of bluetooth permissions that are required to do system routing.
120      *
121      * <p>Alternatively, apps that hold {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} are
122      * also allowed to do system routing.
123      */
124     private static final String[] BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING =
125             new String[] {
126                 Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
127             };
128 
129     private final Context mContext;
130     private final Looper mLooper;
131     private final UserManagerInternal mUserManagerInternal;
132     private final Object mLock = new Object();
133     private final AppOpsManager mAppOpsManager;
134     private final StatusBarManagerInternal mStatusBarManagerInternal;
135     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
136     final ActivityManager mActivityManager;
137     final PowerManager mPowerManager;
138 
139     @GuardedBy("mLock")
140     private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
141     @GuardedBy("mLock")
142     private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
143     @GuardedBy("mLock")
144     private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
145 
146     @GuardedBy("mLock")
147     private int mCurrentActiveUserId = -1;
148 
149     @GuardedBy("mLock")
150     private static final MediaRouterMetricLogger mMediaRouterMetricLogger =
151             new MediaRouterMetricLogger();
152 
153     private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
154             (uid, importance) -> {
155                 synchronized (mLock) {
156                     final int count = mUserRecords.size();
157                     for (int i = 0; i < count; i++) {
158                         mUserRecords.valueAt(i).mHandler.maybeUpdateDiscoveryPreferenceForUid(uid);
159                     }
160                 }
161             };
162 
163     private final BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() {
164         @Override
165         public void onReceive(Context context, Intent intent) {
166             synchronized (mLock) {
167                 final int count = mUserRecords.size();
168                 for (int i = 0; i < count; i++) {
169                     UserHandler userHandler = mUserRecords.valueAt(i).mHandler;
170                     userHandler.sendMessage(PooledLambda.obtainMessage(
171                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
172                 }
173             }
174         }
175     };
176 
177     private final AppOpsManager.OnOpChangedListener mOnOpChangedListener =
178             new AppOpsManager.OnOpChangedListener() {
179                 @Override
180                 public void onOpChanged(String op, String packageName) {
181                     // Do nothing.
182                 }
183 
184                 @Override
185                 public void onOpChanged(
186                         @NonNull String op, @NonNull String packageName, int userId) {
187                     if (!TextUtils.equals(op, AppOpsManager.OPSTR_MEDIA_ROUTING_CONTROL)) {
188                         return;
189                     }
190                     synchronized (mLock) {
191                         revokeManagerRecordAccessIfNeededLocked(packageName, userId);
192                     }
193                 }
194             };
195 
196     @RequiresPermission(
197             allOf = {
198                 Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
199                 Manifest.permission.WATCH_APPOPS
200             })
MediaRouter2ServiceImpl(@onNull Context context, @NonNull Looper looper)201     /* package */ MediaRouter2ServiceImpl(@NonNull Context context, @NonNull Looper looper) {
202         mContext = context;
203         mLooper = looper;
204         mActivityManager = mContext.getSystemService(ActivityManager.class);
205         mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
206                 REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
207         mPowerManager = mContext.getSystemService(PowerManager.class);
208         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
209         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
210         mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
211 
212         IntentFilter screenOnOffIntentFilter = new IntentFilter();
213         screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
214         screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
215         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
216 
217         // Passing null package name to listen to all events.
218         mAppOpsManager.startWatchingMode(
219                 AppOpsManager.OP_MEDIA_ROUTING_CONTROL,
220                 /* packageName */ null,
221                 mOnOpChangedListener);
222 
223         mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
224     }
225 
226     /**
227      * Called when there's a change in the permissions of an app.
228      *
229      * @param uid The uid of the app whose permissions changed.
230      */
onPermissionsChanged(int uid)231     private void onPermissionsChanged(int uid) {
232         synchronized (mLock) {
233             Optional<RouterRecord> affectedRouter =
234                     mAllRouterRecords.values().stream().filter(it -> it.mUid == uid).findFirst();
235             if (affectedRouter.isPresent()) {
236                 affectedRouter.get().maybeUpdateSystemRoutingPermissionLocked();
237             }
238         }
239     }
240 
241     // Start of methods that implement MediaRouter2 operations.
242 
243     @NonNull
getSystemRoutes(@onNull String callerPackageName, boolean isProxyRouter)244     public List<MediaRoute2Info> getSystemRoutes(@NonNull String callerPackageName,
245             boolean isProxyRouter) {
246         final int uid = Binder.getCallingUid();
247         final int pid = Binder.getCallingPid();
248         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
249 
250         boolean hasSystemRoutingPermissions;
251         if (!isProxyRouter) {
252             hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
253         } else {
254             // Request from ProxyRouter.
255             hasSystemRoutingPermissions =
256                     checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
257         }
258 
259         final long token = Binder.clearCallingIdentity();
260         try {
261             Collection<MediaRoute2Info> systemRoutes;
262             synchronized (mLock) {
263                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
264                 if (hasSystemRoutingPermissions) {
265                     MediaRoute2ProviderInfo providerInfo =
266                             userRecord.mHandler.getSystemProvider().getProviderInfo();
267                     if (providerInfo != null) {
268                         systemRoutes = providerInfo.getRoutes();
269                     } else {
270                         systemRoutes = Collections.emptyList();
271                         Slog.e(
272                                 TAG,
273                                 "Returning empty system routes list because "
274                                     + "system provider has null providerInfo.");
275                     }
276                 } else {
277                     systemRoutes = new ArrayList<>();
278                     systemRoutes.add(
279                             userRecord.mHandler.getSystemProvider().getDefaultRoute());
280                 }
281             }
282             return new ArrayList<>(systemRoutes);
283         } finally {
284             Binder.restoreCallingIdentity(token);
285         }
286     }
287 
288     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showMediaOutputSwitcherWithRouter2(@onNull String packageName)289     public boolean showMediaOutputSwitcherWithRouter2(@NonNull String packageName) {
290         UserHandle userHandle = Binder.getCallingUserHandle();
291         final long token = Binder.clearCallingIdentity();
292         try {
293             return showOutputSwitcher(packageName, userHandle);
294         } finally {
295             Binder.restoreCallingIdentity(token);
296         }
297     }
298 
registerRouter2(@onNull IMediaRouter2 router, @NonNull String packageName)299     public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) {
300         Objects.requireNonNull(router, "router must not be null");
301         if (TextUtils.isEmpty(packageName)) {
302             throw new IllegalArgumentException("packageName must not be empty");
303         }
304 
305         final int uid = Binder.getCallingUid();
306         final int pid = Binder.getCallingPid();
307         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
308         final boolean hasConfigureWifiDisplayPermission =
309                 mContext.checkCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY)
310                         == PackageManager.PERMISSION_GRANTED;
311         final boolean hasModifyAudioRoutingPermission =
312                 checkCallerHasModifyAudioRoutingPermission(pid, uid);
313         boolean hasMediaContentControlPermission = checkMediaContentControlPermission(uid, pid);
314         boolean hasMediaRoutingControlPermission =
315                 checkMediaRoutingControlPermission(uid, pid, packageName);
316 
317         final long token = Binder.clearCallingIdentity();
318         try {
319             synchronized (mLock) {
320                 registerRouter2Locked(
321                         router,
322                         uid,
323                         pid,
324                         packageName,
325                         userId,
326                         hasConfigureWifiDisplayPermission,
327                         hasModifyAudioRoutingPermission,
328                         hasMediaContentControlPermission,
329                         hasMediaRoutingControlPermission);
330             }
331         } finally {
332             Binder.restoreCallingIdentity(token);
333         }
334     }
335 
unregisterRouter2(@onNull IMediaRouter2 router)336     public void unregisterRouter2(@NonNull IMediaRouter2 router) {
337         Objects.requireNonNull(router, "router must not be null");
338 
339         final long token = Binder.clearCallingIdentity();
340         try {
341             synchronized (mLock) {
342                 unregisterRouter2Locked(router, false);
343             }
344         } finally {
345             Binder.restoreCallingIdentity(token);
346         }
347     }
348 
349     @RequiresPermission(
350             anyOf = {
351                 Manifest.permission.MEDIA_ROUTING_CONTROL,
352                 Manifest.permission.MEDIA_CONTENT_CONTROL
353             },
354             conditional = true)
updateScanningState( @onNull IMediaRouter2 router, @ScanningState int scanningState)355     public void updateScanningState(
356             @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
357         Objects.requireNonNull(router, "router must not be null");
358         validateScanningStateValue(scanningState);
359 
360         final long token = Binder.clearCallingIdentity();
361         try {
362             synchronized (mLock) {
363                 updateScanningStateLocked(router, scanningState);
364             }
365         } finally {
366             Binder.restoreCallingIdentity(token);
367         }
368     }
369 
setDiscoveryRequestWithRouter2( @onNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference)370     public void setDiscoveryRequestWithRouter2(
371             @NonNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference) {
372         Objects.requireNonNull(router, "router must not be null");
373         Objects.requireNonNull(preference, "preference must not be null");
374 
375         final long token = Binder.clearCallingIdentity();
376         try {
377             synchronized (mLock) {
378                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
379                 if (routerRecord == null) {
380                     Slog.w(TAG, "Ignoring updating discoveryRequest of null routerRecord.");
381                     return;
382                 }
383                 setDiscoveryRequestWithRouter2Locked(routerRecord, preference);
384             }
385         } finally {
386             Binder.restoreCallingIdentity(token);
387         }
388     }
389 
setRouteListingPreference( @onNull IMediaRouter2 router, @Nullable RouteListingPreference routeListingPreference)390     public void setRouteListingPreference(
391             @NonNull IMediaRouter2 router,
392             @Nullable RouteListingPreference routeListingPreference) {
393         ComponentName linkedItemLandingComponent =
394                 routeListingPreference != null
395                         ? routeListingPreference.getLinkedItemComponentName()
396                         : null;
397         if (linkedItemLandingComponent != null) {
398             int callingUid = Binder.getCallingUid();
399             MediaServerUtils.enforcePackageName(
400                     mContext, linkedItemLandingComponent.getPackageName(), callingUid);
401             if (!MediaServerUtils.isValidActivityComponentName(
402                     mContext,
403                     linkedItemLandingComponent,
404                     RouteListingPreference.ACTION_TRANSFER_MEDIA,
405                     Binder.getCallingUserHandle())) {
406                 throw new IllegalArgumentException(
407                         "Unable to resolve "
408                                 + linkedItemLandingComponent
409                                 + " to a valid activity for "
410                                 + RouteListingPreference.ACTION_TRANSFER_MEDIA);
411             }
412         }
413 
414         final long token = Binder.clearCallingIdentity();
415         try {
416             synchronized (mLock) {
417                 RouterRecord routerRecord = mAllRouterRecords.get(router.asBinder());
418                 if (routerRecord == null) {
419                     Slog.w(TAG, "Ignoring updating route listing of null routerRecord.");
420                     return;
421                 }
422                 setRouteListingPreferenceLocked(routerRecord, routeListingPreference);
423             }
424         } finally {
425             Binder.restoreCallingIdentity(token);
426         }
427     }
428 
setRouteVolumeWithRouter2( @onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)429     public void setRouteVolumeWithRouter2(
430             @NonNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume) {
431         Objects.requireNonNull(router, "router must not be null");
432         Objects.requireNonNull(route, "route must not be null");
433 
434         final long token = Binder.clearCallingIdentity();
435         try {
436             synchronized (mLock) {
437                 setRouteVolumeWithRouter2Locked(router, route, volume);
438             }
439         } finally {
440             Binder.restoreCallingIdentity(token);
441         }
442     }
443 
requestCreateSessionWithRouter2( @onNull IMediaRouter2 router, int requestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, Bundle sessionHints)444     public void requestCreateSessionWithRouter2(
445             @NonNull IMediaRouter2 router,
446             int requestId,
447             long managerRequestId,
448             @NonNull RoutingSessionInfo oldSession,
449             @NonNull MediaRoute2Info route,
450             Bundle sessionHints) {
451         Objects.requireNonNull(router, "router must not be null");
452         Objects.requireNonNull(oldSession, "oldSession must not be null");
453         Objects.requireNonNull(route, "route must not be null");
454 
455         final long token = Binder.clearCallingIdentity();
456         try {
457             synchronized (mLock) {
458                 requestCreateSessionWithRouter2Locked(
459                         requestId, managerRequestId, router, oldSession, route, sessionHints);
460             }
461         } finally {
462             Binder.restoreCallingIdentity(token);
463         }
464     }
465 
selectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)466     public void selectRouteWithRouter2(@NonNull IMediaRouter2 router,
467             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
468         Objects.requireNonNull(router, "router must not be null");
469         Objects.requireNonNull(route, "route must not be null");
470         if (TextUtils.isEmpty(uniqueSessionId)) {
471             throw new IllegalArgumentException("uniqueSessionId must not be empty");
472         }
473 
474         final long token = Binder.clearCallingIdentity();
475         try {
476             synchronized (mLock) {
477                 selectRouteWithRouter2Locked(router, uniqueSessionId, route);
478             }
479         } finally {
480             Binder.restoreCallingIdentity(token);
481         }
482     }
483 
deselectRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)484     public void deselectRouteWithRouter2(@NonNull IMediaRouter2 router,
485             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
486         Objects.requireNonNull(router, "router must not be null");
487         Objects.requireNonNull(route, "route must not be null");
488         if (TextUtils.isEmpty(uniqueSessionId)) {
489             throw new IllegalArgumentException("uniqueSessionId must not be empty");
490         }
491 
492         final long token = Binder.clearCallingIdentity();
493         try {
494             synchronized (mLock) {
495                 deselectRouteWithRouter2Locked(router, uniqueSessionId, route);
496             }
497         } finally {
498             Binder.restoreCallingIdentity(token);
499         }
500     }
501 
transferToRouteWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)502     public void transferToRouteWithRouter2(@NonNull IMediaRouter2 router,
503             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
504         Objects.requireNonNull(router, "router must not be null");
505         Objects.requireNonNull(route, "route must not be null");
506         if (TextUtils.isEmpty(uniqueSessionId)) {
507             throw new IllegalArgumentException("uniqueSessionId must not be empty");
508         }
509 
510         UserHandle userHandle = Binder.getCallingUserHandle();
511         final long token = Binder.clearCallingIdentity();
512         try {
513             synchronized (mLock) {
514                 transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
515             }
516         } finally {
517             Binder.restoreCallingIdentity(token);
518         }
519     }
520 
setSessionVolumeWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)521     public void setSessionVolumeWithRouter2(@NonNull IMediaRouter2 router,
522             @NonNull String uniqueSessionId, int volume) {
523         Objects.requireNonNull(router, "router must not be null");
524         Objects.requireNonNull(uniqueSessionId, "uniqueSessionId must not be null");
525         if (TextUtils.isEmpty(uniqueSessionId)) {
526             throw new IllegalArgumentException("uniqueSessionId must not be empty");
527         }
528 
529         final long token = Binder.clearCallingIdentity();
530         try {
531             synchronized (mLock) {
532                 setSessionVolumeWithRouter2Locked(router, uniqueSessionId, volume);
533             }
534         } finally {
535             Binder.restoreCallingIdentity(token);
536         }
537     }
538 
releaseSessionWithRouter2(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)539     public void releaseSessionWithRouter2(@NonNull IMediaRouter2 router,
540             @NonNull String uniqueSessionId) {
541         Objects.requireNonNull(router, "router must not be null");
542         if (TextUtils.isEmpty(uniqueSessionId)) {
543             throw new IllegalArgumentException("uniqueSessionId must not be empty");
544         }
545 
546         final long token = Binder.clearCallingIdentity();
547         try {
548             synchronized (mLock) {
549                 releaseSessionWithRouter2Locked(router, uniqueSessionId);
550             }
551         } finally {
552             Binder.restoreCallingIdentity(token);
553         }
554     }
555 
setDeviceSuggestionsWithRouter2( @onNull IMediaRouter2 router, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo)556     public void setDeviceSuggestionsWithRouter2(
557             @NonNull IMediaRouter2 router,
558             @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
559         Objects.requireNonNull(router, "router must not be null");
560 
561         final long token = Binder.clearCallingIdentity();
562         try {
563             synchronized (mLock) {
564                 setDeviceSuggestionsWithRouter2Locked(router, suggestedDeviceInfo);
565             }
566         } finally {
567             Binder.restoreCallingIdentity(token);
568         }
569     }
570 
571     @Nullable
getDeviceSuggestionsWithRouter2( @onNull IMediaRouter2 router)572     public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2(
573             @NonNull IMediaRouter2 router) {
574         Objects.requireNonNull(router, "router must not be null");
575 
576         final long token = Binder.clearCallingIdentity();
577         try {
578             synchronized (mLock) {
579                 return getDeviceSuggestionsWithRouter2Locked(router);
580             }
581         } finally {
582             Binder.restoreCallingIdentity(token);
583         }
584     }
585 
586     // End of methods that implement MediaRouter2 operations.
587 
588     // Start of methods that implement MediaRouter2Manager operations.
589 
590     @NonNull
getRemoteSessions(@onNull IMediaRouter2Manager manager)591     public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) {
592         Objects.requireNonNull(manager, "manager must not be null");
593         final long token = Binder.clearCallingIdentity();
594         try {
595             synchronized (mLock) {
596                 return getRemoteSessionsLocked(manager);
597             }
598         } finally {
599             Binder.restoreCallingIdentity(token);
600         }
601     }
602 
603     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
registerManager(@onNull IMediaRouter2Manager manager, @NonNull String callerPackageName)604     public void registerManager(@NonNull IMediaRouter2Manager manager,
605             @NonNull String callerPackageName) {
606         Objects.requireNonNull(manager, "manager must not be null");
607         if (TextUtils.isEmpty(callerPackageName)) {
608             throw new IllegalArgumentException("callerPackageName must not be empty");
609         }
610 
611         final int callerUid = Binder.getCallingUid();
612         final int callerPid = Binder.getCallingPid();
613         final UserHandle callerUser = Binder.getCallingUserHandle();
614 
615         enforcePrivilegedRoutingPermissions(callerUid, callerPid, callerPackageName);
616 
617         final long token = Binder.clearCallingIdentity();
618         try {
619             synchronized (mLock) {
620                 registerManagerLocked(
621                         manager,
622                         callerUid,
623                         callerPid,
624                         callerPackageName,
625                         /* targetPackageName */ null,
626                         callerUser);
627             }
628         } finally {
629             Binder.restoreCallingIdentity(token);
630         }
631     }
632 
633     @RequiresPermission(
634             anyOf = {
635                 Manifest.permission.MEDIA_CONTENT_CONTROL,
636                 Manifest.permission.MEDIA_ROUTING_CONTROL
637             })
registerProxyRouter( @onNull IMediaRouter2Manager manager, @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull UserHandle targetUser)638     public void registerProxyRouter(
639             @NonNull IMediaRouter2Manager manager,
640             @NonNull String callerPackageName,
641             @NonNull String targetPackageName,
642             @NonNull UserHandle targetUser) {
643         Objects.requireNonNull(manager, "manager must not be null");
644         Objects.requireNonNull(targetUser, "targetUser must not be null");
645 
646         if (TextUtils.isEmpty(targetPackageName)) {
647             throw new IllegalArgumentException("targetPackageName must not be empty");
648         }
649 
650         int callerUid = Binder.getCallingUid();
651         int callerPid = Binder.getCallingPid();
652         final long token = Binder.clearCallingIdentity();
653 
654         try {
655             enforcePrivilegedRoutingPermissions(callerUid, callerPid, callerPackageName);
656             enforceCrossUserPermissions(callerUid, callerPid, targetUser);
657             if (!verifyPackageExistsForUser(targetPackageName, targetUser)) {
658                 throw new IllegalArgumentException(
659                         "targetPackageName does not exist: " + targetPackageName);
660             }
661 
662             synchronized (mLock) {
663                 registerManagerLocked(
664                         manager,
665                         callerUid,
666                         callerPid,
667                         callerPackageName,
668                         targetPackageName,
669                         targetUser);
670             }
671         } finally {
672             Binder.restoreCallingIdentity(token);
673         }
674     }
675 
unregisterManager(@onNull IMediaRouter2Manager manager)676     public void unregisterManager(@NonNull IMediaRouter2Manager manager) {
677         Objects.requireNonNull(manager, "manager must not be null");
678 
679         final long token = Binder.clearCallingIdentity();
680         try {
681             synchronized (mLock) {
682                 unregisterManagerLocked(manager, false);
683             }
684         } finally {
685             Binder.restoreCallingIdentity(token);
686         }
687     }
688 
updateScanningState( @onNull IMediaRouter2Manager manager, @ScanningState int scanningState)689     public void updateScanningState(
690             @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
691         Objects.requireNonNull(manager, "manager must not be null");
692         validateScanningStateValue(scanningState);
693 
694         final long token = Binder.clearCallingIdentity();
695         try {
696             synchronized (mLock) {
697                 updateScanningStateLocked(manager, scanningState);
698             }
699         } finally {
700             Binder.restoreCallingIdentity(token);
701         }
702     }
703 
setRouteVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull MediaRoute2Info route, int volume)704     public void setRouteVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
705             @NonNull MediaRoute2Info route, int volume) {
706         Objects.requireNonNull(manager, "manager must not be null");
707         Objects.requireNonNull(route, "route must not be null");
708 
709         final long token = Binder.clearCallingIdentity();
710         try {
711             synchronized (mLock) {
712                 setRouteVolumeWithManagerLocked(requestId, manager, route, volume);
713             }
714         } finally {
715             Binder.restoreCallingIdentity(token);
716         }
717     }
718 
requestCreateSessionWithManager( @onNull IMediaRouter2Manager manager, int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)719     public void requestCreateSessionWithManager(
720             @NonNull IMediaRouter2Manager manager,
721             int requestId,
722             @NonNull RoutingSessionInfo oldSession,
723             @NonNull MediaRoute2Info route) {
724         Objects.requireNonNull(manager, "manager must not be null");
725         Objects.requireNonNull(oldSession, "oldSession must not be null");
726         Objects.requireNonNull(route, "route must not be null");
727 
728         final long token = Binder.clearCallingIdentity();
729         try {
730             synchronized (mLock) {
731                 requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
732             }
733         } finally {
734             Binder.restoreCallingIdentity(token);
735         }
736     }
737 
selectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)738     public void selectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
739             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
740         Objects.requireNonNull(manager, "manager must not be null");
741         if (TextUtils.isEmpty(uniqueSessionId)) {
742             throw new IllegalArgumentException("uniqueSessionId must not be empty");
743         }
744         Objects.requireNonNull(route, "route must not be null");
745 
746         final long token = Binder.clearCallingIdentity();
747         try {
748             synchronized (mLock) {
749                 selectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
750             }
751         } finally {
752             Binder.restoreCallingIdentity(token);
753         }
754     }
755 
deselectRouteWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)756     public void deselectRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
757             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
758         Objects.requireNonNull(manager, "manager must not be null");
759         if (TextUtils.isEmpty(uniqueSessionId)) {
760             throw new IllegalArgumentException("uniqueSessionId must not be empty");
761         }
762         Objects.requireNonNull(route, "route must not be null");
763 
764         final long token = Binder.clearCallingIdentity();
765         try {
766             synchronized (mLock) {
767                 deselectRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
768             }
769         } finally {
770             Binder.restoreCallingIdentity(token);
771         }
772     }
773 
transferToRouteWithManager( @onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)774     public void transferToRouteWithManager(
775             @NonNull IMediaRouter2Manager manager,
776             int requestId,
777             @NonNull String uniqueSessionId,
778             @NonNull MediaRoute2Info route,
779             @NonNull UserHandle transferInitiatorUserHandle,
780             @NonNull String transferInitiatorPackageName) {
781         Objects.requireNonNull(manager, "manager must not be null");
782         if (TextUtils.isEmpty(uniqueSessionId)) {
783             throw new IllegalArgumentException("uniqueSessionId must not be empty");
784         }
785         Objects.requireNonNull(route, "route must not be null");
786         Objects.requireNonNull(transferInitiatorUserHandle);
787         Objects.requireNonNull(transferInitiatorPackageName);
788 
789         final long token = Binder.clearCallingIdentity();
790         try {
791             synchronized (mLock) {
792                 transferToRouteWithManagerLocked(
793                         requestId,
794                         manager,
795                         uniqueSessionId,
796                         route,
797                         RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
798                         transferInitiatorUserHandle,
799                         transferInitiatorPackageName);
800             }
801         } finally {
802             Binder.restoreCallingIdentity(token);
803         }
804     }
805 
setSessionVolumeWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId, int volume)806     public void setSessionVolumeWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
807             @NonNull String uniqueSessionId, int volume) {
808         Objects.requireNonNull(manager, "manager must not be null");
809         if (TextUtils.isEmpty(uniqueSessionId)) {
810             throw new IllegalArgumentException("uniqueSessionId must not be empty");
811         }
812 
813         final long token = Binder.clearCallingIdentity();
814         try {
815             synchronized (mLock) {
816                 setSessionVolumeWithManagerLocked(requestId, manager, uniqueSessionId, volume);
817             }
818         } finally {
819             Binder.restoreCallingIdentity(token);
820         }
821     }
822 
releaseSessionWithManager(@onNull IMediaRouter2Manager manager, int requestId, @NonNull String uniqueSessionId)823     public void releaseSessionWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
824             @NonNull String uniqueSessionId) {
825         Objects.requireNonNull(manager, "manager must not be null");
826         if (TextUtils.isEmpty(uniqueSessionId)) {
827             throw new IllegalArgumentException("uniqueSessionId must not be empty");
828         }
829 
830         final long token = Binder.clearCallingIdentity();
831         try {
832             synchronized (mLock) {
833                 releaseSessionWithManagerLocked(requestId, manager, uniqueSessionId);
834             }
835         } finally {
836             Binder.restoreCallingIdentity(token);
837         }
838     }
839 
setDeviceSuggestionsWithManager( @onNull IMediaRouter2Manager manager, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo)840     public void setDeviceSuggestionsWithManager(
841             @NonNull IMediaRouter2Manager manager,
842             @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
843         Objects.requireNonNull(manager, "manager must not be null");
844 
845         final long token = Binder.clearCallingIdentity();
846         try {
847             synchronized (mLock) {
848                 setDeviceSuggestionsWithManagerLocked(manager, suggestedDeviceInfo);
849             }
850         } finally {
851             Binder.restoreCallingIdentity(token);
852         }
853     }
854 
855     @Nullable
getDeviceSuggestionsWithManager( @onNull IMediaRouter2Manager manager)856     public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager(
857             @NonNull IMediaRouter2Manager manager) {
858         Objects.requireNonNull(manager, "manager must not be null");
859 
860         final long token = Binder.clearCallingIdentity();
861         try {
862             synchronized (mLock) {
863                 return getDeviceSuggestionsWithManagerLocked(manager);
864             }
865         } finally {
866             Binder.restoreCallingIdentity(token);
867         }
868     }
869 
870     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showMediaOutputSwitcherWithProxyRouter( @onNull IMediaRouter2Manager proxyRouter)871     public boolean showMediaOutputSwitcherWithProxyRouter(
872             @NonNull IMediaRouter2Manager proxyRouter) {
873         Objects.requireNonNull(proxyRouter, "Proxy router must not be null");
874 
875         final long token = Binder.clearCallingIdentity();
876         try {
877             synchronized (mLock) {
878                 final IBinder binder = proxyRouter.asBinder();
879                 ManagerRecord proxyRouterRecord = mAllManagerRecords.get(binder);
880 
881                 if (proxyRouterRecord.mTargetPackageName == null) {
882                     throw new UnsupportedOperationException(
883                             "Only proxy routers can show the Output Switcher.");
884                 }
885 
886                 return showOutputSwitcher(
887                         proxyRouterRecord.mTargetPackageName,
888                         UserHandle.of(proxyRouterRecord.mUserRecord.mUserId));
889             }
890         } finally {
891             Binder.restoreCallingIdentity(token);
892         }
893     }
894 
895     // End of methods that implement MediaRouter2Manager operations.
896 
897     // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
898 
899     @Nullable
getSystemSessionInfo( @onNull String callerPackageName, @Nullable String targetPackageName, boolean setDeviceRouteSelected)900     public RoutingSessionInfo getSystemSessionInfo(
901             @NonNull String callerPackageName,
902             @Nullable String targetPackageName,
903             boolean setDeviceRouteSelected) {
904         final int uid = Binder.getCallingUid();
905         final int pid = Binder.getCallingPid();
906         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
907 
908         boolean hasSystemRoutingPermissions;
909         if (targetPackageName == null) {
910             hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
911         } else {
912             // Request from ProxyRouter.
913             hasSystemRoutingPermissions =
914                     checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
915         }
916 
917         final long token = Binder.clearCallingIdentity();
918         try {
919             synchronized (mLock) {
920                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
921                 SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider();
922                 if (hasSystemRoutingPermissions) {
923                     if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) {
924                         // Return a fake system session that shows the device route as selected and
925                         // available bluetooth routes as transferable.
926                         return systemProvider.generateDeviceRouteSelectedSessionInfo(
927                                 targetPackageName);
928                     } else {
929                         RoutingSessionInfo session =
930                                 systemProvider.getSessionForPackage(targetPackageName);
931                         if (session != null) {
932                             return session;
933                         } else {
934                             Slog.w(TAG, "System provider does not have any session info.");
935                             return null;
936                         }
937                     }
938                 } else {
939                     return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo())
940                             .setClientPackageName(targetPackageName)
941                             .build();
942                 }
943             }
944         } finally {
945             Binder.restoreCallingIdentity(token);
946         }
947     }
948 
checkCallerHasSystemRoutingPermissions(int pid, int uid)949     private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
950         return checkCallerHasModifyAudioRoutingPermission(pid, uid)
951                 || checkCallerHasBluetoothPermissions(pid, uid);
952     }
953 
checkCallerHasPrivilegedRoutingPermissions( int pid, int uid, @NonNull String callerPackageName)954     private boolean checkCallerHasPrivilegedRoutingPermissions(
955             int pid, int uid, @NonNull String callerPackageName) {
956         return checkMediaContentControlPermission(uid, pid)
957                 || checkMediaRoutingControlPermission(uid, pid, callerPackageName);
958     }
959 
checkCallerHasModifyAudioRoutingPermission(int pid, int uid)960     private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
961         return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
962                 == PackageManager.PERMISSION_GRANTED;
963     }
964 
checkCallerHasBluetoothPermissions(int pid, int uid)965     private boolean checkCallerHasBluetoothPermissions(int pid, int uid) {
966         boolean hasBluetoothRoutingPermission = true;
967         for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
968             hasBluetoothRoutingPermission &=
969                     mContext.checkPermission(permission, pid, uid)
970                             == PackageManager.PERMISSION_GRANTED;
971         }
972         return hasBluetoothRoutingPermission;
973     }
974 
975     @RequiresPermission(
976             anyOf = {
977                 Manifest.permission.MEDIA_ROUTING_CONTROL,
978                 Manifest.permission.MEDIA_CONTENT_CONTROL
979             })
enforcePrivilegedRoutingPermissions( int callerUid, int callerPid, @NonNull String callerPackageName)980     private void enforcePrivilegedRoutingPermissions(
981             int callerUid, int callerPid, @NonNull String callerPackageName) {
982         if (checkMediaContentControlPermission(callerUid, callerPid)) {
983             return;
984         }
985 
986         if (!checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName)) {
987             throw new SecurityException(
988                     "Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
989         }
990     }
991 
checkMediaContentControlPermission(int callerUid, int callerPid)992     private boolean checkMediaContentControlPermission(int callerUid, int callerPid) {
993         return mContext.checkPermission(
994                         Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid)
995                 == PackageManager.PERMISSION_GRANTED;
996     }
997 
checkMediaRoutingControlPermission( int callerUid, int callerPid, @NonNull String callerPackageName)998     private boolean checkMediaRoutingControlPermission(
999             int callerUid, int callerPid, @NonNull String callerPackageName) {
1000         if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
1001             return false;
1002         }
1003 
1004         return PermissionChecker.checkPermissionForDataDelivery(
1005                         mContext,
1006                         Manifest.permission.MEDIA_ROUTING_CONTROL,
1007                         callerPid,
1008                         callerUid,
1009                         callerPackageName,
1010                         /* attributionTag */ null,
1011                         /* message */ "Checking permissions for registering manager in"
1012                                 + " MediaRouter2ServiceImpl.")
1013                 == PermissionChecker.PERMISSION_GRANTED;
1014     }
1015 
1016     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS)
verifyPackageExistsForUser( @onNull String clientPackageName, @NonNull UserHandle user)1017     private boolean verifyPackageExistsForUser(
1018             @NonNull String clientPackageName, @NonNull UserHandle user) {
1019         try {
1020             PackageManager pm = mContext.getPackageManager();
1021             pm.getPackageInfoAsUser(
1022                     clientPackageName, PackageManager.PackageInfoFlags.of(0), user.getIdentifier());
1023             return true;
1024         } catch (PackageManager.NameNotFoundException ex) {
1025             return false;
1026         }
1027     }
1028 
1029     /**
1030      * Enforces the caller has {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} if the
1031      * caller's user is different from the target user.
1032      */
enforceCrossUserPermissions( int callerUid, int callerPid, @NonNull UserHandle targetUser)1033     private void enforceCrossUserPermissions(
1034             int callerUid, int callerPid, @NonNull UserHandle targetUser) {
1035         int callerUserId = UserHandle.getUserId(callerUid);
1036 
1037         if (targetUser.getIdentifier() != callerUserId) {
1038             mContext.enforcePermission(
1039                     Manifest.permission.INTERACT_ACROSS_USERS_FULL,
1040                     callerPid,
1041                     callerUid,
1042                     "Must hold INTERACT_ACROSS_USERS_FULL to control an app in a different"
1043                             + " userId.");
1044         }
1045     }
1046 
1047     @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
showOutputSwitcher( @onNull String packageName, @NonNull UserHandle userHandle)1048     private boolean showOutputSwitcher(
1049             @NonNull String packageName, @NonNull UserHandle userHandle) {
1050         if (mActivityManager.getPackageImportance(packageName) > IMPORTANCE_FOREGROUND) {
1051             Slog.w(TAG, "showMediaOutputSwitcher only works when called from foreground");
1052             return false;
1053         }
1054         synchronized (mLock) {
1055             mStatusBarManagerInternal.showMediaOutputSwitcher(packageName, userHandle);
1056         }
1057         return true;
1058     }
1059 
1060     // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
1061 
dump(@onNull PrintWriter pw, @NonNull String prefix)1062     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
1063         pw.println(prefix + "MediaRouter2ServiceImpl");
1064 
1065         String indent = prefix + "  ";
1066 
1067         synchronized (mLock) {
1068             pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
1069             pw.println(indent + "mCurrentActiveUserId=" + mCurrentActiveUserId);
1070 
1071             pw.println(indent + "UserRecords:");
1072             if (mUserRecords.size() > 0) {
1073                 for (int i = 0; i < mUserRecords.size(); i++) {
1074                     mUserRecords.valueAt(i).dump(pw, indent + "  ");
1075                 }
1076             } else {
1077                 pw.println(indent + "  <no user records>");
1078             }
1079         }
1080     }
1081 
updateRunningUserAndProfiles(int newActiveUserId)1082     /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
1083         synchronized (mLock) {
1084             if (mCurrentActiveUserId != newActiveUserId) {
1085                 Slog.i(TAG, TextUtils.formatSimple(
1086                         "switchUser | user: %d", newActiveUserId));
1087 
1088                 mCurrentActiveUserId = newActiveUserId;
1089                 // disposeUserIfNeededLocked might modify the collection, hence clone
1090                 final var userRecords = mUserRecords.clone();
1091                 for (int i = 0; i < userRecords.size(); i++) {
1092                     int userId = userRecords.keyAt(i);
1093                     UserRecord userRecord = userRecords.valueAt(i);
1094                     if (isUserActiveLocked(userId)) {
1095                         // userId corresponds to the active user, or one of its profiles. We
1096                         // ensure the associated structures are initialized.
1097                         userRecord.mHandler.sendMessage(
1098                                 obtainMessage(UserHandler::start, userRecord.mHandler));
1099                     } else {
1100                         userRecord.mHandler.sendMessage(
1101                                 obtainMessage(UserHandler::stop, userRecord.mHandler));
1102                         disposeUserIfNeededLocked(userRecord);
1103                     }
1104                 }
1105             }
1106         }
1107     }
1108 
routerDied(@onNull RouterRecord routerRecord)1109     void routerDied(@NonNull RouterRecord routerRecord) {
1110         synchronized (mLock) {
1111             unregisterRouter2Locked(routerRecord.mRouter, true);
1112         }
1113     }
1114 
managerDied(@onNull ManagerRecord managerRecord)1115     void managerDied(@NonNull ManagerRecord managerRecord) {
1116         synchronized (mLock) {
1117             unregisterManagerLocked(managerRecord.mManager, true);
1118         }
1119     }
1120 
1121     /**
1122      * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
1123      * of the active user, returns {@code false} otherwise.
1124      */
1125     @GuardedBy("mLock")
isUserActiveLocked(int userId)1126     private boolean isUserActiveLocked(int userId) {
1127         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
1128     }
1129 
1130     @GuardedBy("mLock")
revokeManagerRecordAccessIfNeededLocked(@onNull String packageName, int userId)1131     private void revokeManagerRecordAccessIfNeededLocked(@NonNull String packageName, int userId) {
1132         UserRecord userRecord = mUserRecords.get(userId);
1133         if (userRecord == null) {
1134             return;
1135         }
1136 
1137         List<ManagerRecord> managers =
1138                 userRecord.mManagerRecords.stream()
1139                         .filter(r -> !r.mHasMediaContentControl)
1140                         .filter(r -> TextUtils.equals(r.mOwnerPackageName, packageName))
1141                         .collect(Collectors.toList());
1142 
1143         if (managers.isEmpty()) {
1144             return;
1145         }
1146 
1147         ManagerRecord record = managers.getFirst();
1148 
1149         // Uid and package name are shared across all manager records in the list.
1150         boolean isAppOpAllowed =
1151                 mAppOpsManager.unsafeCheckOpNoThrow(
1152                                 AppOpsManager.OPSTR_MEDIA_ROUTING_CONTROL,
1153                                 record.mOwnerUid,
1154                                 record.mOwnerPackageName)
1155                         == AppOpsManager.MODE_ALLOWED;
1156 
1157         if (isAppOpAllowed) {
1158             return;
1159         }
1160 
1161         for (ManagerRecord manager : managers) {
1162             boolean isRegularPermission =
1163                     mContext.checkPermission(
1164                                     Manifest.permission.MEDIA_ROUTING_CONTROL,
1165                                     manager.mOwnerPid,
1166                                     manager.mOwnerUid)
1167                             == PackageManager.PERMISSION_GRANTED;
1168 
1169             if (isRegularPermission) {
1170                 // We should check the regular permission for all manager records, as different PIDs
1171                 // might yield different permission results.
1172                 continue;
1173             }
1174 
1175             Slog.w(TAG, "Revoking access for " + manager.getDebugString());
1176             unregisterManagerLocked(manager.mManager, /* died */ false);
1177             try {
1178                 manager.mManager.invalidateInstance();
1179             } catch (RemoteException ex) {
1180                 manager.logRemoteException("invalidateInstance", ex);
1181             }
1182         }
1183     }
1184 
1185     // Start of locked methods that are used by MediaRouter2.
1186 
1187     @GuardedBy("mLock")
registerRouter2Locked( @onNull IMediaRouter2 router, int uid, int pid, @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission, boolean hasMediaContentControlPermission, boolean hasMediaRoutingControlPermission)1188     private void registerRouter2Locked(
1189             @NonNull IMediaRouter2 router,
1190             int uid,
1191             int pid,
1192             @NonNull String packageName,
1193             int userId,
1194             boolean hasConfigureWifiDisplayPermission,
1195             boolean hasModifyAudioRoutingPermission,
1196             boolean hasMediaContentControlPermission,
1197             boolean hasMediaRoutingControlPermission) {
1198         final IBinder binder = router.asBinder();
1199         if (mAllRouterRecords.get(binder) != null) {
1200             Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName="
1201                     + packageName);
1202             return;
1203         }
1204 
1205         UserRecord userRecord = getOrCreateUserRecordLocked(userId);
1206         RouterRecord routerRecord =
1207                 new RouterRecord(
1208                         mContext,
1209                         userRecord,
1210                         router,
1211                         uid,
1212                         pid,
1213                         packageName,
1214                         hasConfigureWifiDisplayPermission,
1215                         hasModifyAudioRoutingPermission,
1216                         hasMediaContentControlPermission,
1217                         hasMediaRoutingControlPermission);
1218         try {
1219             binder.linkToDeath(routerRecord, 0);
1220         } catch (RemoteException ex) {
1221             throw new RuntimeException("MediaRouter2 died prematurely.", ex);
1222         }
1223 
1224         userRecord.mRouterRecords.add(routerRecord);
1225         mAllRouterRecords.put(binder, routerRecord);
1226 
1227         userRecord.mHandler.sendMessage(
1228                 obtainMessage(UserHandler::notifyRouterRegistered,
1229                         userRecord.mHandler, routerRecord));
1230 
1231         Slog.i(
1232                 TAG,
1233                 TextUtils.formatSimple(
1234                         "registerRouter2 | package: %s, uid: %d, pid: %d, router id: %d,"
1235                                 + " hasMediaRoutingControl: %b",
1236                         packageName,
1237                         uid,
1238                         pid,
1239                         routerRecord.mRouterId,
1240                         hasMediaRoutingControlPermission));
1241     }
1242 
1243     @GuardedBy("mLock")
unregisterRouter2Locked(@onNull IMediaRouter2 router, boolean died)1244     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
1245         RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
1246         if (routerRecord == null) {
1247             Slog.w(
1248                     TAG,
1249                     TextUtils.formatSimple(
1250                             "Ignoring unregistering unknown router: %s, died: %b", router, died));
1251             return;
1252         }
1253 
1254         Slog.i(
1255                 TAG,
1256                 TextUtils.formatSimple(
1257                         "unregisterRouter2 | package: %s, router id: %d, died: %b",
1258                         routerRecord.mPackageName, routerRecord.mRouterId, died));
1259 
1260         UserRecord userRecord = routerRecord.mUserRecord;
1261         userRecord.mRouterRecords.remove(routerRecord);
1262         routerRecord.mUserRecord.mHandler.sendMessage(
1263                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
1264                         routerRecord.mUserRecord.mHandler,
1265                         routerRecord.mPackageName, null));
1266         routerRecord.mUserRecord.mHandler.sendMessage(
1267                 obtainMessage(
1268                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
1269                         routerRecord.mUserRecord.mHandler,
1270                         routerRecord.mPackageName,
1271                         /* routeListingPreference= */ null));
1272         userRecord.mHandler.sendMessage(
1273                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
1274                         userRecord.mHandler));
1275         routerRecord.dispose();
1276         disposeUserIfNeededLocked(userRecord); // since router removed from user
1277     }
1278 
1279     @RequiresPermission(
1280             anyOf = {
1281                 Manifest.permission.MEDIA_ROUTING_CONTROL,
1282                 Manifest.permission.MEDIA_CONTENT_CONTROL
1283             },
1284             conditional = true)
1285     @GuardedBy("mLock")
updateScanningStateLocked( @onNull IMediaRouter2 router, @ScanningState int scanningState)1286     private void updateScanningStateLocked(
1287             @NonNull IMediaRouter2 router, @ScanningState int scanningState) {
1288         final IBinder binder = router.asBinder();
1289         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1290         if (routerRecord == null) {
1291             Slog.w(TAG, "Router record not found. Ignoring updateScanningState call.");
1292             return;
1293         }
1294 
1295         boolean enableScanViaMediaContentControl =
1296                 Flags.enableFullScanWithMediaContentControl()
1297                         && routerRecord.mHasMediaContentControlPermission;
1298         if (scanningState == SCANNING_STATE_SCANNING_FULL
1299                 && !enableScanViaMediaContentControl
1300                 && !routerRecord.mHasMediaRoutingControl) {
1301             throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
1302         }
1303 
1304         Slog.i(
1305                 TAG,
1306                 TextUtils.formatSimple(
1307                         "updateScanningStateLocked | router: %d, packageName: %s, scanningState:"
1308                             + " %d",
1309                         routerRecord.mRouterId,
1310                         routerRecord.mPackageName,
1311                         getScanningStateString(scanningState)));
1312 
1313         routerRecord.updateScanningState(scanningState);
1314     }
1315 
1316     @GuardedBy("mLock")
setDiscoveryRequestWithRouter2Locked(@onNull RouterRecord routerRecord, @NonNull RouteDiscoveryPreference discoveryRequest)1317     private void setDiscoveryRequestWithRouter2Locked(@NonNull RouterRecord routerRecord,
1318             @NonNull RouteDiscoveryPreference discoveryRequest) {
1319         if (routerRecord.mDiscoveryPreference.equals(discoveryRequest)) {
1320             return;
1321         }
1322 
1323         Slog.i(
1324                 TAG,
1325                 TextUtils.formatSimple(
1326                         "setDiscoveryRequestWithRouter2 | router: %s(id: %d), discovery request:"
1327                             + " %s",
1328                         routerRecord.mPackageName,
1329                         routerRecord.mRouterId,
1330                         discoveryRequest.toString()));
1331 
1332         routerRecord.mDiscoveryPreference = discoveryRequest;
1333         routerRecord.mUserRecord.mHandler.sendMessage(
1334                 obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
1335                         routerRecord.mUserRecord.mHandler,
1336                         routerRecord.mPackageName,
1337                         routerRecord.mDiscoveryPreference));
1338         routerRecord.mUserRecord.mHandler.sendMessage(
1339                 obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
1340                         routerRecord.mUserRecord.mHandler));
1341     }
1342 
1343     @GuardedBy("mLock")
setRouteListingPreferenceLocked( RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference)1344     private void setRouteListingPreferenceLocked(
1345             RouterRecord routerRecord, @Nullable RouteListingPreference routeListingPreference) {
1346         routerRecord.mRouteListingPreference = routeListingPreference;
1347         String routeListingAsString =
1348                 routeListingPreference != null
1349                         ? routeListingPreference.getItems().stream()
1350                                 .map(RouteListingPreference.Item::getRouteId)
1351                                 .collect(Collectors.joining(","))
1352                         : null;
1353 
1354         Slog.i(
1355                 TAG,
1356                 TextUtils.formatSimple(
1357                         "setRouteListingPreference | router: %s(id: %d), route listing preference:"
1358                             + " [%s]",
1359                         routerRecord.mPackageName, routerRecord.mRouterId, routeListingAsString));
1360 
1361         routerRecord.mUserRecord.mHandler.sendMessage(
1362                 obtainMessage(
1363                         UserHandler::notifyRouteListingPreferenceChangeToManagers,
1364                         routerRecord.mUserRecord.mHandler,
1365                         routerRecord.mPackageName,
1366                         routeListingPreference));
1367     }
1368 
1369     @GuardedBy("mLock")
setRouteVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume)1370     private void setRouteVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
1371             @NonNull MediaRoute2Info route, int volume) {
1372         final IBinder binder = router.asBinder();
1373         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1374 
1375         if (routerRecord != null) {
1376             Slog.i(
1377                     TAG,
1378                     TextUtils.formatSimple(
1379                             "setRouteVolumeWithRouter2 | router: %s(id: %d), volume: %d",
1380                             routerRecord.mPackageName, routerRecord.mRouterId, volume));
1381 
1382             routerRecord.mUserRecord.mHandler.sendMessage(
1383                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
1384                             routerRecord.mUserRecord.mHandler,
1385                             DUMMY_REQUEST_ID, route, volume));
1386         }
1387     }
1388 
1389     @GuardedBy("mLock")
requestCreateSessionWithRouter2Locked( int requestId, long managerRequestId, @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)1390     private void requestCreateSessionWithRouter2Locked(
1391             int requestId,
1392             long managerRequestId,
1393             @NonNull IMediaRouter2 router,
1394             @NonNull RoutingSessionInfo oldSession,
1395             @NonNull MediaRoute2Info route,
1396             @Nullable Bundle sessionHints) {
1397         final IBinder binder = router.asBinder();
1398         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1399 
1400         if (routerRecord == null) {
1401             mMediaRouterMetricLogger.logOperationFailure(
1402                     MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
1403                     MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
1404             return;
1405         }
1406 
1407         Slog.i(
1408                 TAG,
1409                 TextUtils.formatSimple(
1410                         "requestCreateSessionWithRouter2 | router: %s(id: %d), old session id: %s,"
1411                             + " new session's route id: %s, request id: %d",
1412                         routerRecord.mPackageName,
1413                         routerRecord.mRouterId,
1414                         oldSession.getId(),
1415                         route.getId(),
1416                         requestId));
1417 
1418         UserHandler userHandler = routerRecord.mUserRecord.mHandler;
1419         if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
1420             ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId));
1421             if (manager == null || manager.mLastSessionCreationRequest == null) {
1422                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: Ignoring unknown request.");
1423                 mMediaRouterMetricLogger.logOperationFailure(
1424                         MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
1425                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND);
1426                 routerRecord.notifySessionCreationFailed(requestId);
1427                 return;
1428             }
1429             if (!TextUtils.equals(
1430                     manager.mLastSessionCreationRequest.mOldSession.getId(), oldSession.getId())) {
1431                 Slog.w(
1432                         TAG,
1433                         "requestCreateSessionWithRouter2Locked: "
1434                                 + "Ignoring unmatched routing session.");
1435                 mMediaRouterMetricLogger.logOperationFailure(
1436                         MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
1437                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
1438                 routerRecord.notifySessionCreationFailed(requestId);
1439                 return;
1440             }
1441             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
1442                     route.getId())) {
1443                 // When media router has no permission
1444                 if (!routerRecord.hasSystemRoutingPermission()
1445                         && manager.mLastSessionCreationRequest.mRoute.isSystemRoute()
1446                         && route.isSystemRoute()) {
1447                     route = manager.mLastSessionCreationRequest.mRoute;
1448                 } else {
1449                     Slog.w(
1450                             TAG,
1451                             "requestCreateSessionWithRouter2Locked: "
1452                                     + "Ignoring unmatched route.");
1453                     mMediaRouterMetricLogger.logOperationFailure(
1454                             MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
1455                             MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID);
1456                     routerRecord.notifySessionCreationFailed(requestId);
1457                     return;
1458                 }
1459             }
1460             manager.mLastSessionCreationRequest = null;
1461         } else {
1462             String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
1463             if (route.isSystemRoute()
1464                     && !routerRecord.hasSystemRoutingPermission()
1465                     && !TextUtils.equals(route.getId(), defaultRouteId)) {
1466                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to" + route);
1467                 mMediaRouterMetricLogger.logOperationFailure(
1468                         MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
1469                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED);
1470                 routerRecord.notifySessionCreationFailed(requestId);
1471                 return;
1472             }
1473         }
1474 
1475         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
1476         mMediaRouterMetricLogger.addRequestInfo(
1477                 uniqueRequestId,
1478                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION);
1479         userHandler.sendMessage(
1480                 obtainMessage(
1481                         UserHandler::requestCreateSessionWithRouter2OnHandler,
1482                         userHandler,
1483                         uniqueRequestId,
1484                         managerRequestId,
1485                         routerRecord,
1486                         oldSession,
1487                         route,
1488                         sessionHints));
1489     }
1490 
1491     @GuardedBy("mLock")
selectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1492     private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1493             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1494         final IBinder binder = router.asBinder();
1495         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1496 
1497         if (routerRecord == null) {
1498             mMediaRouterMetricLogger.logOperationFailure(
1499                     MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
1500                     MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
1501             return;
1502         }
1503 
1504         Slog.i(
1505                 TAG,
1506                 TextUtils.formatSimple(
1507                         "selectRouteWithRouter2 | router: %s(id: %d), route: %s",
1508                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1509         mMediaRouterMetricLogger.logOperationTriggered(
1510                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
1511                 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
1512 
1513         routerRecord.mUserRecord.mHandler.sendMessage(
1514                 obtainMessage(UserHandler::selectRouteOnHandler,
1515                         routerRecord.mUserRecord.mHandler,
1516                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1517     }
1518 
1519     @GuardedBy("mLock")
deselectRouteWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1520     private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
1521             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1522         final IBinder binder = router.asBinder();
1523         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1524 
1525         if (routerRecord == null) {
1526             mMediaRouterMetricLogger.logOperationFailure(
1527                     MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
1528                     MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
1529             return;
1530         }
1531 
1532         Slog.i(
1533                 TAG,
1534                 TextUtils.formatSimple(
1535                         "deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
1536                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1537         mMediaRouterMetricLogger.logOperationTriggered(
1538                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
1539                 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
1540 
1541         routerRecord.mUserRecord.mHandler.sendMessage(
1542                 obtainMessage(UserHandler::deselectRouteOnHandler,
1543                         routerRecord.mUserRecord.mHandler,
1544                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
1545     }
1546 
1547     @GuardedBy("mLock")
transferToRouteWithRouter2Locked( @onNull IMediaRouter2 router, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1548     private void transferToRouteWithRouter2Locked(
1549             @NonNull IMediaRouter2 router,
1550             @NonNull UserHandle transferInitiatorUserHandle,
1551             @NonNull String uniqueSessionId,
1552             @NonNull MediaRoute2Info route) {
1553         final IBinder binder = router.asBinder();
1554         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1555 
1556         if (routerRecord == null) {
1557             mMediaRouterMetricLogger.logOperationFailure(
1558                     MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
1559                     MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
1560             return;
1561         }
1562 
1563         Slog.i(
1564                 TAG,
1565                 TextUtils.formatSimple(
1566                         "transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
1567                         routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
1568         mMediaRouterMetricLogger.logOperationTriggered(
1569                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
1570                 MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
1571 
1572         UserHandler userHandler = routerRecord.mUserRecord.mHandler;
1573         String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
1574         if (route.isSystemRoute()
1575                 && !routerRecord.hasSystemRoutingPermission()
1576                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
1577             userHandler.sendMessage(
1578                     obtainMessage(
1579                             RouterRecord::notifySessionCreationFailed,
1580                             routerRecord,
1581                             toOriginalRequestId(DUMMY_REQUEST_ID)));
1582         } else {
1583             userHandler.sendMessage(
1584                     obtainMessage(
1585                             UserHandler::transferToRouteOnHandler,
1586                             userHandler,
1587                             DUMMY_REQUEST_ID,
1588                             transferInitiatorUserHandle,
1589                             routerRecord.mPackageName,
1590                             routerRecord,
1591                             uniqueSessionId,
1592                             route,
1593                             RoutingSessionInfo.TRANSFER_REASON_APP));
1594         }
1595     }
1596 
1597     @GuardedBy("mLock")
setSessionVolumeWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId, int volume)1598     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
1599             @NonNull String uniqueSessionId, int volume) {
1600         final IBinder binder = router.asBinder();
1601         RouterRecord routerRecord = mAllRouterRecords.get(binder);
1602 
1603         if (routerRecord == null) {
1604             return;
1605         }
1606 
1607         Slog.i(
1608                 TAG,
1609                 TextUtils.formatSimple(
1610                         "setSessionVolumeWithRouter2 | router: %s(id: %d), session: %s, volume: %d",
1611                         routerRecord.mPackageName,
1612                         routerRecord.mRouterId,
1613                         uniqueSessionId,
1614                         volume));
1615 
1616         routerRecord.mUserRecord.mHandler.sendMessage(
1617                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
1618                         routerRecord.mUserRecord.mHandler,
1619                         DUMMY_REQUEST_ID, uniqueSessionId, volume));
1620     }
1621 
1622     @GuardedBy("mLock")
releaseSessionWithRouter2Locked(@onNull IMediaRouter2 router, @NonNull String uniqueSessionId)1623     private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
1624             @NonNull String uniqueSessionId) {
1625         final IBinder binder = router.asBinder();
1626         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1627 
1628         if (routerRecord == null) {
1629             mMediaRouterMetricLogger.logOperationFailure(
1630                     MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION,
1631                     MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
1632             return;
1633         }
1634 
1635         Slog.i(
1636                 TAG,
1637                 TextUtils.formatSimple(
1638                         "releaseSessionWithRouter2 | router: %s(id: %d), session: %s",
1639                         routerRecord.mPackageName, routerRecord.mRouterId, uniqueSessionId));
1640 
1641         routerRecord.mUserRecord.mHandler.sendMessage(
1642                 obtainMessage(UserHandler::releaseSessionOnHandler,
1643                         routerRecord.mUserRecord.mHandler,
1644                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
1645     }
1646 
1647     @GuardedBy("mLock")
setDeviceSuggestionsWithRouter2Locked( @onNull IMediaRouter2 router, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo)1648     private void setDeviceSuggestionsWithRouter2Locked(
1649             @NonNull IMediaRouter2 router,
1650             @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
1651         final IBinder binder = router.asBinder();
1652         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1653 
1654         if (routerRecord == null) {
1655             Slog.w(
1656                     TAG,
1657                     TextUtils.formatSimple(
1658                             "Ignoring set device suggestion for unknown router: %s", router));
1659             return;
1660         }
1661 
1662         Slog.i(
1663                 TAG,
1664                 TextUtils.formatSimple(
1665                         "setDeviceSuggestions | router: %d suggestion: %d",
1666                         routerRecord.mPackageName, suggestedDeviceInfo));
1667 
1668         routerRecord.mUserRecord.updateDeviceSuggestionsLocked(
1669                 routerRecord.mPackageName, routerRecord.mPackageName, suggestedDeviceInfo);
1670         routerRecord.mUserRecord.mHandler.sendMessage(
1671                 obtainMessage(
1672                         UserHandler::notifyDeviceSuggestionsUpdatedOnHandler,
1673                         routerRecord.mUserRecord.mHandler,
1674                         routerRecord.mPackageName,
1675                         routerRecord.mPackageName,
1676                         suggestedDeviceInfo));
1677     }
1678 
1679     @GuardedBy("mLock")
1680     @Nullable
getDeviceSuggestionsWithRouter2Locked( @onNull IMediaRouter2 router)1681     private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2Locked(
1682             @NonNull IMediaRouter2 router) {
1683         final IBinder binder = router.asBinder();
1684         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
1685 
1686         if (routerRecord == null) {
1687             Slog.w(
1688                     TAG,
1689                     TextUtils.formatSimple(
1690                             "Attempted to get device suggestion for unknown router: %s", router));
1691             return null;
1692         }
1693 
1694         Slog.i(
1695                 TAG,
1696                 TextUtils.formatSimple(
1697                         "getDeviceSuggestions | router: %d", routerRecord.mPackageName));
1698 
1699         return routerRecord.mUserRecord.getDeviceSuggestionsLocked(routerRecord.mPackageName);
1700     }
1701 
1702     // End of locked methods that are used by MediaRouter2.
1703 
1704     // Start of locked methods that are used by MediaRouter2Manager.
1705 
1706     @GuardedBy("mLock")
getRemoteSessionsLocked( @onNull IMediaRouter2Manager manager)1707     private List<RoutingSessionInfo> getRemoteSessionsLocked(
1708             @NonNull IMediaRouter2Manager manager) {
1709         final IBinder binder = manager.asBinder();
1710         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1711 
1712         if (managerRecord == null) {
1713             Slog.w(TAG, "getRemoteSessionLocked: Ignoring unknown manager");
1714             return Collections.emptyList();
1715         }
1716 
1717         List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
1718         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mRouteProviders) {
1719             if (!provider.mIsSystemRouteProvider) {
1720                 sessionInfos.addAll(provider.getSessionInfos());
1721             }
1722         }
1723         return sessionInfos;
1724     }
1725 
1726     @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
1727     @GuardedBy("mLock")
registerManagerLocked( @onNull IMediaRouter2Manager manager, int callerUid, int callerPid, @NonNull String callerPackageName, @Nullable String targetPackageName, @NonNull UserHandle targetUser)1728     private void registerManagerLocked(
1729             @NonNull IMediaRouter2Manager manager,
1730             int callerUid,
1731             int callerPid,
1732             @NonNull String callerPackageName,
1733             @Nullable String targetPackageName,
1734             @NonNull UserHandle targetUser) {
1735         final IBinder binder = manager.asBinder();
1736         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1737 
1738         if (managerRecord != null) {
1739             Slog.w(TAG, "registerManagerLocked: Same manager already exists. callerPackageName="
1740                     + callerPackageName);
1741             return;
1742         }
1743 
1744         boolean hasMediaRoutingControl =
1745                 checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName);
1746 
1747         boolean hasMediaContentControl = checkMediaContentControlPermission(callerUid, callerPid);
1748 
1749         Slog.i(
1750                 TAG,
1751                 TextUtils.formatSimple(
1752                         "registerManager | callerUid: %d, callerPid: %d, callerPackage: %s,"
1753                             + " targetPackageName: %s, targetUserId: %d, hasMediaRoutingControl:"
1754                             + " %b",
1755                         callerUid,
1756                         callerPid,
1757                         callerPackageName,
1758                         targetPackageName,
1759                         targetUser,
1760                         hasMediaRoutingControl));
1761 
1762         UserRecord userRecord = getOrCreateUserRecordLocked(targetUser.getIdentifier());
1763 
1764         managerRecord =
1765                 new ManagerRecord(
1766                         userRecord,
1767                         manager,
1768                         callerUid,
1769                         callerPid,
1770                         callerPackageName,
1771                         targetPackageName,
1772                         hasMediaRoutingControl,
1773                         hasMediaContentControl);
1774         try {
1775             binder.linkToDeath(managerRecord, 0);
1776         } catch (RemoteException ex) {
1777             throw new RuntimeException("Media router manager died prematurely.", ex);
1778         }
1779 
1780         userRecord.mManagerRecords.add(managerRecord);
1781         mAllManagerRecords.put(binder, managerRecord);
1782 
1783         // Note: Features should be sent first before the routes. If not, the
1784         // RouteCallback#onRoutesAdded() for system MR2 will never be called with initial routes
1785         // due to the lack of features.
1786         for (RouterRecord routerRecord : userRecord.mRouterRecords) {
1787             // Send route listing preferences before discovery preferences and routes to avoid an
1788             // inconsistent state where there are routes to show, but the manager thinks
1789             // the app has not expressed a preference for listing.
1790             userRecord.mHandler.sendMessage(
1791                     obtainMessage(
1792                             UserHandler::notifyRouteListingPreferenceChangeToManagers,
1793                             routerRecord.mUserRecord.mHandler,
1794                             routerRecord.mPackageName,
1795                             routerRecord.mRouteListingPreference));
1796             // TODO: UserRecord <-> routerRecord, why do they reference each other?
1797             // How about removing mUserRecord from routerRecord?
1798             routerRecord.mUserRecord.mHandler.sendMessage(
1799                     obtainMessage(
1800                             UserHandler::notifyDiscoveryPreferenceChangedToManager,
1801                             routerRecord.mUserRecord.mHandler,
1802                             routerRecord,
1803                             manager));
1804         }
1805 
1806         userRecord.mHandler.sendMessage(
1807                 obtainMessage(
1808                         UserHandler::dispatchRoutesToManagerOnHandler,
1809                         userRecord.mHandler,
1810                         managerRecord));
1811     }
1812 
1813     @GuardedBy("mLock")
unregisterManagerLocked(@onNull IMediaRouter2Manager manager, boolean died)1814     private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
1815         ManagerRecord managerRecord = mAllManagerRecords.remove(manager.asBinder());
1816         if (managerRecord == null) {
1817             Slog.w(
1818                     TAG,
1819                     TextUtils.formatSimple(
1820                             "Ignoring unregistering unknown manager: %s, died: %b", manager, died));
1821             return;
1822         }
1823         UserRecord userRecord = managerRecord.mUserRecord;
1824 
1825         Slog.i(
1826                 TAG,
1827                 TextUtils.formatSimple(
1828                         "unregisterManager | package: %s, user: %d, manager: %d, died: %b",
1829                         managerRecord.mOwnerPackageName,
1830                         userRecord.mUserId,
1831                         managerRecord.mManagerId,
1832                         died));
1833 
1834         userRecord.mManagerRecords.remove(managerRecord);
1835         managerRecord.dispose();
1836         disposeUserIfNeededLocked(userRecord); // since manager removed from user
1837     }
1838 
1839     @GuardedBy("mLock")
updateScanningStateLocked( @onNull IMediaRouter2Manager manager, @ScanningState int scanningState)1840     private void updateScanningStateLocked(
1841             @NonNull IMediaRouter2Manager manager, @ScanningState int scanningState) {
1842         final IBinder binder = manager.asBinder();
1843         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1844         if (managerRecord == null) {
1845             Slog.w(TAG, "Manager record not found. Ignoring updateScanningState call.");
1846             return;
1847         }
1848 
1849         boolean enableScanViaMediaContentControl =
1850                 Flags.enableFullScanWithMediaContentControl()
1851                         && managerRecord.mHasMediaContentControl;
1852         if (!managerRecord.mHasMediaRoutingControl
1853                 && !enableScanViaMediaContentControl
1854                 && scanningState == SCANNING_STATE_SCANNING_FULL) {
1855             throw new SecurityException("Screen off scan requires MEDIA_ROUTING_CONTROL");
1856         }
1857 
1858         Slog.i(
1859                 TAG,
1860                 TextUtils.formatSimple(
1861                         "updateScanningState | manager: %d, ownerPackageName: %s,"
1862                             + " targetPackageName: %s, scanningState: %d",
1863                         managerRecord.mManagerId,
1864                         managerRecord.mOwnerPackageName,
1865                         managerRecord.mTargetPackageName,
1866                         getScanningStateString(scanningState)));
1867 
1868         managerRecord.updateScanningState(scanningState);
1869     }
1870 
1871     @GuardedBy("mLock")
setRouteVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull MediaRoute2Info route, int volume)1872     private void setRouteVolumeWithManagerLocked(int requestId,
1873             @NonNull IMediaRouter2Manager manager,
1874             @NonNull MediaRoute2Info route, int volume) {
1875         final IBinder binder = manager.asBinder();
1876         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1877 
1878         if (managerRecord == null) {
1879             return;
1880         }
1881 
1882         Slog.i(TAG, TextUtils.formatSimple(
1883                 "setRouteVolumeWithManager | manager: %d, route: %s, volume: %d",
1884                 managerRecord.mManagerId, route.getId(), volume));
1885 
1886         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1887         managerRecord.mUserRecord.mHandler.sendMessage(
1888                 obtainMessage(UserHandler::setRouteVolumeOnHandler,
1889                         managerRecord.mUserRecord.mHandler,
1890                         uniqueRequestId, route, volume));
1891     }
1892 
1893     @GuardedBy("mLock")
requestCreateSessionWithManagerLocked( int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)1894     private void requestCreateSessionWithManagerLocked(
1895             int requestId,
1896             @NonNull IMediaRouter2Manager manager,
1897             @NonNull RoutingSessionInfo oldSession,
1898             @NonNull MediaRoute2Info route) {
1899         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
1900         if (managerRecord == null) {
1901             return;
1902         }
1903 
1904         Slog.i(TAG, TextUtils.formatSimple(
1905                 "requestCreateSessionWithManager | manager: %d, route: %s",
1906                 managerRecord.mManagerId, route.getId()));
1907 
1908         String packageName = oldSession.getClientPackageName();
1909 
1910         RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName);
1911         if (routerRecord == null) {
1912             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
1913                     + "unknown router.");
1914             managerRecord.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
1915             return;
1916         }
1917 
1918         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1919         SessionCreationRequest lastRequest = managerRecord.mLastSessionCreationRequest;
1920         if (lastRequest != null) {
1921             Slog.i(
1922                     TAG,
1923                     TextUtils.formatSimple(
1924                             "requestCreateSessionWithManagerLocked: Notifying failure for pending"
1925                                 + " session creation request - oldSession: %s, route: %s",
1926                             lastRequest.mOldSession, lastRequest.mRoute));
1927             managerRecord.notifyRequestFailed(
1928                     toOriginalRequestId(lastRequest.mManagerRequestId), REASON_UNKNOWN_ERROR);
1929         }
1930         managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
1931                 MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
1932                 oldSession, route);
1933 
1934         // Before requesting to the provider, get session hints from the media router.
1935         // As a return, media router will request to create a session.
1936         routerRecord.mUserRecord.mHandler.sendMessage(
1937                 obtainMessage(
1938                         RouterRecord::requestCreateSessionByManager,
1939                         routerRecord,
1940                         managerRecord,
1941                         uniqueRequestId,
1942                         oldSession,
1943                         route));
1944     }
1945 
1946     @GuardedBy("mLock")
selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1947     private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
1948             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1949         final IBinder binder = manager.asBinder();
1950         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1951 
1952         if (managerRecord == null) {
1953             return;
1954         }
1955 
1956         Slog.i(TAG, TextUtils.formatSimple(
1957                 "selectRouteWithManager | manager: %d, session: %s, route: %s",
1958                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1959 
1960         // Can be null if the session is system's or RCN.
1961         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1962                 .findRouterWithSessionLocked(uniqueSessionId);
1963 
1964         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1965         mMediaRouterMetricLogger.addRequestInfo(
1966                 uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE);
1967 
1968         managerRecord.mUserRecord.mHandler.sendMessage(
1969                 obtainMessage(UserHandler::selectRouteOnHandler,
1970                         managerRecord.mUserRecord.mHandler,
1971                         uniqueRequestId, routerRecord, uniqueSessionId, route));
1972     }
1973 
1974     @GuardedBy("mLock")
deselectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)1975     private void deselectRouteWithManagerLocked(int requestId,
1976             @NonNull IMediaRouter2Manager manager,
1977             @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
1978         final IBinder binder = manager.asBinder();
1979         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
1980 
1981         if (managerRecord == null) {
1982             return;
1983         }
1984 
1985         Slog.i(TAG, TextUtils.formatSimple(
1986                 "deselectRouteWithManager | manager: %d, session: %s, route: %s",
1987                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
1988 
1989         // Can be null if the session is system's or RCN.
1990         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
1991                 .findRouterWithSessionLocked(uniqueSessionId);
1992 
1993         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
1994         mMediaRouterMetricLogger.addRequestInfo(
1995                 uniqueRequestId,
1996                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE);
1997 
1998         managerRecord.mUserRecord.mHandler.sendMessage(
1999                 obtainMessage(UserHandler::deselectRouteOnHandler,
2000                         managerRecord.mUserRecord.mHandler,
2001                         uniqueRequestId, routerRecord, uniqueSessionId, route));
2002     }
2003 
2004     @GuardedBy("mLock")
transferToRouteWithManagerLocked( int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)2005     private void transferToRouteWithManagerLocked(
2006             int requestId,
2007             @NonNull IMediaRouter2Manager manager,
2008             @NonNull String uniqueSessionId,
2009             @NonNull MediaRoute2Info route,
2010             @RoutingSessionInfo.TransferReason int transferReason,
2011             @NonNull UserHandle transferInitiatorUserHandle,
2012             @NonNull String transferInitiatorPackageName) {
2013         final IBinder binder = manager.asBinder();
2014         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
2015 
2016         if (managerRecord == null) {
2017             return;
2018         }
2019 
2020         Slog.i(TAG, TextUtils.formatSimple(
2021                 "transferToRouteWithManager | manager: %d, session: %s, route: %s",
2022                 managerRecord.mManagerId, uniqueSessionId, route.getId()));
2023 
2024         // Can be null if the session is system's or RCN.
2025         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
2026                 .findRouterWithSessionLocked(uniqueSessionId);
2027 
2028         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
2029         mMediaRouterMetricLogger.addRequestInfo(
2030                 uniqueRequestId,
2031                 MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE);
2032 
2033         managerRecord.mUserRecord.mHandler.sendMessage(
2034                 obtainMessage(
2035                         UserHandler::transferToRouteOnHandler,
2036                         managerRecord.mUserRecord.mHandler,
2037                         uniqueRequestId,
2038                         transferInitiatorUserHandle,
2039                         transferInitiatorPackageName,
2040                         routerRecord,
2041                         uniqueSessionId,
2042                         route,
2043                         transferReason));
2044     }
2045 
2046     @GuardedBy("mLock")
setSessionVolumeWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId, int volume)2047     private void setSessionVolumeWithManagerLocked(int requestId,
2048             @NonNull IMediaRouter2Manager manager,
2049             @NonNull String uniqueSessionId, int volume) {
2050         final IBinder binder = manager.asBinder();
2051         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
2052 
2053         if (managerRecord == null) {
2054             return;
2055         }
2056 
2057         Slog.i(TAG, TextUtils.formatSimple(
2058                 "setSessionVolumeWithManager | manager: %d, session: %s, volume: %d",
2059                 managerRecord.mManagerId, uniqueSessionId, volume));
2060 
2061         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
2062         managerRecord.mUserRecord.mHandler.sendMessage(
2063                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
2064                         managerRecord.mUserRecord.mHandler,
2065                         uniqueRequestId, uniqueSessionId, volume));
2066     }
2067 
2068     @GuardedBy("mLock")
releaseSessionWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId)2069     private void releaseSessionWithManagerLocked(int requestId,
2070             @NonNull IMediaRouter2Manager manager, @NonNull String uniqueSessionId) {
2071         final IBinder binder = manager.asBinder();
2072         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
2073 
2074         if (managerRecord == null) {
2075             return;
2076         }
2077 
2078         Slog.i(TAG, TextUtils.formatSimple(
2079                 "releaseSessionWithManager | manager: %d, session: %s",
2080                 managerRecord.mManagerId, uniqueSessionId));
2081 
2082         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
2083                 .findRouterWithSessionLocked(uniqueSessionId);
2084 
2085         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
2086         managerRecord.mUserRecord.mHandler.sendMessage(
2087                 obtainMessage(UserHandler::releaseSessionOnHandler,
2088                         managerRecord.mUserRecord.mHandler,
2089                         uniqueRequestId, routerRecord, uniqueSessionId));
2090     }
2091 
2092     @GuardedBy("mLock")
setDeviceSuggestionsWithManagerLocked( @onNull IMediaRouter2Manager manager, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo)2093     private void setDeviceSuggestionsWithManagerLocked(
2094             @NonNull IMediaRouter2Manager manager,
2095             @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
2096         final IBinder binder = manager.asBinder();
2097         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
2098 
2099         if (managerRecord == null || managerRecord.mTargetPackageName == null) {
2100             Slog.w(
2101                     TAG,
2102                     TextUtils.formatSimple(
2103                             "Ignoring set device suggestion for unknown manager: %s", manager));
2104             return;
2105         }
2106 
2107         Slog.i(
2108                 TAG,
2109                 TextUtils.formatSimple(
2110                         "setDeviceSuggestions | manager: %d, suggestingPackageName: %d suggestion:"
2111                             + " %d",
2112                         managerRecord.mManagerId,
2113                         managerRecord.mOwnerPackageName,
2114                         suggestedDeviceInfo));
2115 
2116         managerRecord.mUserRecord.updateDeviceSuggestionsLocked(
2117                 managerRecord.mTargetPackageName,
2118                 managerRecord.mOwnerPackageName,
2119                 suggestedDeviceInfo);
2120         managerRecord.mUserRecord.mHandler.sendMessage(
2121                 obtainMessage(
2122                         UserHandler::notifyDeviceSuggestionsUpdatedOnHandler,
2123                         managerRecord.mUserRecord.mHandler,
2124                         managerRecord.mTargetPackageName,
2125                         managerRecord.mOwnerPackageName,
2126                         suggestedDeviceInfo));
2127     }
2128 
2129     @GuardedBy("mLock")
2130     @Nullable
getDeviceSuggestionsWithManagerLocked( @onNull IMediaRouter2Manager manager)2131     private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManagerLocked(
2132             @NonNull IMediaRouter2Manager manager) {
2133         final IBinder binder = manager.asBinder();
2134         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
2135 
2136         if (managerRecord == null || managerRecord.mTargetPackageName == null) {
2137             Slog.w(
2138                     TAG,
2139                     TextUtils.formatSimple(
2140                             "Attempted to get device suggestion for unknown manager: %s", manager));
2141             return null;
2142         }
2143 
2144         Slog.i(
2145                 TAG,
2146                 TextUtils.formatSimple(
2147                         "getDeviceSuggestionsWithManagerLocked | manager: %d",
2148                         managerRecord.mManagerId));
2149 
2150         return managerRecord.mUserRecord.getDeviceSuggestionsLocked(
2151                 managerRecord.mTargetPackageName);
2152     }
2153 
2154     // End of locked methods that are used by MediaRouter2Manager.
2155 
2156     // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
2157 
2158     @GuardedBy("mLock")
getOrCreateUserRecordLocked(int userId)2159     private UserRecord getOrCreateUserRecordLocked(int userId) {
2160         UserRecord userRecord = mUserRecords.get(userId);
2161         if (userRecord == null) {
2162             userRecord = new UserRecord(userId, mLooper);
2163             mUserRecords.put(userId, userRecord);
2164             userRecord.init();
2165             if (isUserActiveLocked(userId)) {
2166                 userRecord.mHandler.sendMessage(
2167                         obtainMessage(UserHandler::start, userRecord.mHandler));
2168             }
2169         }
2170         return userRecord;
2171     }
2172 
2173     @GuardedBy("mLock")
disposeUserIfNeededLocked(@onNull UserRecord userRecord)2174     private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
2175         // If there are no records left and the user is no longer current then go ahead
2176         // and purge the user record and all of its associated state.  If the user is current
2177         // then leave it alone since we might be connected to a route or want to query
2178         // the same route information again soon.
2179         if (!isUserActiveLocked(userRecord.mUserId)
2180                 && userRecord.mRouterRecords.isEmpty()
2181                 && userRecord.mManagerRecords.isEmpty()) {
2182             if (DEBUG) {
2183                 Slog.d(TAG, userRecord + ": Disposed");
2184             }
2185             userRecord.mHandler.sendMessage(
2186                     obtainMessage(UserHandler::stop, userRecord.mHandler));
2187             mUserRecords.remove(userRecord.mUserId);
2188             // Note: User already stopped (by switchUser) so no need to send stop message here.
2189         }
2190     }
2191 
2192     // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
2193 
toUniqueRequestId(int requesterId, int originalRequestId)2194     static long toUniqueRequestId(int requesterId, int originalRequestId) {
2195         return ((long) requesterId << 32) | originalRequestId;
2196     }
2197 
toRequesterId(long uniqueRequestId)2198     static int toRequesterId(long uniqueRequestId) {
2199         return (int) (uniqueRequestId >> 32);
2200     }
2201 
toOriginalRequestId(long uniqueRequestId)2202     static int toOriginalRequestId(long uniqueRequestId) {
2203         return (int) uniqueRequestId;
2204     }
2205 
getScanningStateString(@canningState int scanningState)2206     private static String getScanningStateString(@ScanningState int scanningState) {
2207         return switch (scanningState) {
2208             case SCANNING_STATE_NOT_SCANNING -> "NOT_SCANNING";
2209             case SCANNING_STATE_WHILE_INTERACTIVE -> "SCREEN_ON_ONLY";
2210             case SCANNING_STATE_SCANNING_FULL -> "FULL";
2211             default -> "Invalid scanning state: " + scanningState;
2212         };
2213     }
2214 
validateScanningStateValue(@canningState int scanningState)2215     private static void validateScanningStateValue(@ScanningState int scanningState) {
2216         if (scanningState != SCANNING_STATE_NOT_SCANNING
2217                 && scanningState != SCANNING_STATE_WHILE_INTERACTIVE
2218                 && scanningState != SCANNING_STATE_SCANNING_FULL) {
2219             throw new IllegalArgumentException(
2220                     TextUtils.formatSimple("Scanning state %d is not valid.", scanningState));
2221         }
2222     }
2223 
2224     final class UserRecord {
2225         public final int mUserId;
2226         //TODO: make records private for thread-safety
2227         final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
2228         final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
2229 
2230         // @GuardedBy("mLock")
2231         private final Map<String, Map<String, List<SuggestedDeviceInfo>>> mDeviceSuggestions =
2232                 new HashMap<>();
2233 
2234         RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
2235         Set<String> mActivelyScanningPackages = Set.of();
2236         final UserHandler mHandler;
2237 
UserRecord(int userId, @NonNull Looper looper)2238         UserRecord(int userId, @NonNull Looper looper) {
2239             mUserId = userId;
2240             mHandler =
2241                     new UserHandler(
2242                             /* service= */ MediaRouter2ServiceImpl.this,
2243                             /* userRecord= */ this,
2244                             looper);
2245         }
2246 
init()2247         void init() {
2248             mHandler.init();
2249         }
2250 
2251         // TODO: This assumes that only one router exists in a package.
2252         //       Do this in Android S or later.
2253         @GuardedBy("mLock")
findRouterRecordLocked(String packageName)2254         RouterRecord findRouterRecordLocked(String packageName) {
2255             for (RouterRecord routerRecord : mRouterRecords) {
2256                 if (TextUtils.equals(routerRecord.mPackageName, packageName)) {
2257                     return routerRecord;
2258                 }
2259             }
2260             return null;
2261         }
2262 
2263         // @GuardedBy("mLock")
updateDeviceSuggestionsLocked( String packageName, String suggestingPackageName, List<SuggestedDeviceInfo> deviceSuggestions)2264         public void updateDeviceSuggestionsLocked(
2265                 String packageName,
2266                 String suggestingPackageName,
2267                 List<SuggestedDeviceInfo> deviceSuggestions) {
2268             mDeviceSuggestions.putIfAbsent(
2269                     packageName, new HashMap<String, List<SuggestedDeviceInfo>>());
2270             Map<String, List<SuggestedDeviceInfo>> suggestions =
2271                     mDeviceSuggestions.get(packageName);
2272             suggestions.put(suggestingPackageName, deviceSuggestions);
2273         }
2274 
2275         // @GuardedBy("mLock")
2276         @Nullable
getDeviceSuggestionsLocked( String packageName)2277         public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsLocked(
2278                 String packageName) {
2279             return mDeviceSuggestions.get(packageName);
2280         }
2281 
dump(@onNull PrintWriter pw, @NonNull String prefix)2282         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2283             pw.println(prefix + "UserRecord");
2284 
2285             String indent = prefix + "  ";
2286 
2287             pw.println(indent + "mUserId=" + mUserId);
2288 
2289             pw.println(indent + "Router Records:");
2290             if (!mRouterRecords.isEmpty()) {
2291                 for (RouterRecord routerRecord : mRouterRecords) {
2292                     routerRecord.dump(pw, indent + "  ");
2293                 }
2294             } else {
2295                 pw.println(indent + "<no router records>");
2296             }
2297 
2298             pw.println(indent + "Manager Records:");
2299             if (!mManagerRecords.isEmpty()) {
2300                 for (ManagerRecord managerRecord : mManagerRecords) {
2301                     managerRecord.dump(pw, indent + "  ");
2302                 }
2303             } else {
2304                 pw.println(indent + "<no manager records>");
2305             }
2306 
2307             pw.println(indent + "Composite discovery preference:");
2308             mCompositeDiscoveryPreference.dump(pw, indent + "  ");
2309             pw.println(
2310                     indent
2311                             + "Packages actively scanning: "
2312                             + String.join(", ", mActivelyScanningPackages));
2313 
2314             if (!mHandler.runWithScissors(() -> mHandler.dump(pw, indent), 1000)) {
2315                 pw.println(indent + "<could not dump handler state>");
2316             }
2317         }
2318     }
2319 
2320     final class RouterRecord implements IBinder.DeathRecipient {
2321         public final Context mContext;
2322         public final UserRecord mUserRecord;
2323         public final String mPackageName;
2324         public final List<Integer> mSelectRouteSequenceNumbers;
2325         public final IMediaRouter2 mRouter;
2326         public final int mUid;
2327         public final int mPid;
2328         public final boolean mHasConfigureWifiDisplayPermission;
2329         public final boolean mHasModifyAudioRoutingPermission;
2330         public final boolean mHasMediaContentControlPermission;
2331         public final boolean mHasMediaRoutingControl;
2332         public final AtomicBoolean mHasBluetoothRoutingPermission;
2333         public final int mRouterId;
2334         public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
2335 
2336         public RouteDiscoveryPreference mDiscoveryPreference;
2337         @Nullable public RouteListingPreference mRouteListingPreference;
2338 
RouterRecord( Context context, UserRecord userRecord, IMediaRouter2 router, int uid, int pid, String packageName, boolean hasConfigureWifiDisplayPermission, boolean hasModifyAudioRoutingPermission, boolean hasMediaContentControlPermission, boolean hasMediaRoutingControl)2339         RouterRecord(
2340                 Context context,
2341                 UserRecord userRecord,
2342                 IMediaRouter2 router,
2343                 int uid,
2344                 int pid,
2345                 String packageName,
2346                 boolean hasConfigureWifiDisplayPermission,
2347                 boolean hasModifyAudioRoutingPermission,
2348                 boolean hasMediaContentControlPermission,
2349                 boolean hasMediaRoutingControl) {
2350             mContext = context;
2351             mUserRecord = userRecord;
2352             mPackageName = packageName;
2353             mSelectRouteSequenceNumbers = new ArrayList<>();
2354             mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
2355             mRouter = router;
2356             mUid = uid;
2357             mPid = pid;
2358             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
2359             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
2360             mHasMediaContentControlPermission = hasMediaContentControlPermission;
2361             mHasMediaRoutingControl = hasMediaRoutingControl;
2362             mHasBluetoothRoutingPermission =
2363                     new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
2364             mRouterId = mNextRouterOrManagerId.getAndIncrement();
2365         }
2366 
2367         /**
2368          * Returns whether the corresponding router has permission to query and control system
2369          * routes.
2370          */
hasSystemRoutingPermission()2371         public boolean hasSystemRoutingPermission() {
2372             return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
2373         }
2374 
isActivelyScanning()2375         public boolean isActivelyScanning() {
2376             return mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
2377                     || mScanningState == SCANNING_STATE_SCANNING_FULL
2378                     || mDiscoveryPreference.shouldPerformActiveScan();
2379         }
2380 
2381         @GuardedBy("mLock")
maybeUpdateSystemRoutingPermissionLocked()2382         public void maybeUpdateSystemRoutingPermissionLocked() {
2383             boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
2384             mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid));
2385             boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
2386             if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
2387                 // TODO: b/379788233 - Ensure access to fields like
2388                 // mLastNotifiedRoutesToPrivilegedRouters happens on the right thread. We might need
2389                 // to run this on the handler.
2390                 Map<String, MediaRoute2Info> routesToReport =
2391                         newSystemRoutingPermissionValue
2392                                 ? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
2393                                 : mUserRecord.mHandler.mLastNotifiedRoutesToNonPrivilegedRouters;
2394                 notifyRoutesUpdated(routesToReport.values().stream().toList());
2395 
2396                 List<RoutingSessionInfo> sessionInfos =
2397                         mUserRecord.mHandler.getSystemProvider().getSessionInfos();
2398                 RoutingSessionInfo systemSessionToReport =
2399                         newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
2400                                 ? sessionInfos.get(0)
2401                                 : mUserRecord.mHandler.getSystemProvider().getDefaultSessionInfo();
2402                 notifySessionInfoChanged(systemSessionToReport);
2403             }
2404         }
2405 
dispose()2406         public void dispose() {
2407             mRouter.asBinder().unlinkToDeath(this, 0);
2408         }
2409 
2410         @Override
binderDied()2411         public void binderDied() {
2412             routerDied(this);
2413         }
2414 
updateScanningState(@canningState int scanningState)2415         public void updateScanningState(@ScanningState int scanningState) {
2416             if (mScanningState == scanningState) {
2417                 return;
2418             }
2419 
2420             mScanningState = scanningState;
2421 
2422             mUserRecord.mHandler.sendMessage(
2423                     obtainMessage(
2424                             UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
2425         }
2426 
dump(@onNull PrintWriter pw, @NonNull String prefix)2427         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2428             pw.println(prefix + "RouterRecord");
2429 
2430             String indent = prefix + "  ";
2431 
2432             pw.println(indent + "mPackageName=" + mPackageName);
2433             pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
2434             pw.println(indent + "mUid=" + mUid);
2435             pw.println(indent + "mPid=" + mPid);
2436             pw.println(indent + "mHasConfigureWifiDisplayPermission="
2437                     + mHasConfigureWifiDisplayPermission);
2438             pw.println(
2439                     indent
2440                             + "mHasModifyAudioRoutingPermission="
2441                             + mHasModifyAudioRoutingPermission);
2442             pw.println(
2443                     indent
2444                             + "mHasBluetoothRoutingPermission="
2445                             + mHasBluetoothRoutingPermission.get());
2446             pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
2447             pw.println(indent + "mRouterId=" + mRouterId);
2448 
2449             mDiscoveryPreference.dump(pw, indent);
2450         }
2451 
2452         /**
2453          * Notifies the corresponding router that it was successfully registered.
2454          *
2455          * <p>The message sent to the router includes a snapshot of the initial state, including
2456          * known routes and the system {@link RoutingSessionInfo}.
2457          *
2458          * @param currentRoutes All currently known routes, which are filtered according to package
2459          *     visibility before being sent to the router.
2460          * @param currentSystemSessionInfo The current system {@link RoutingSessionInfo}.
2461          */
notifyRegistered( List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo)2462         public void notifyRegistered(
2463                 List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) {
2464             try {
2465                 mRouter.notifyRouterRegistered(
2466                         getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
2467             } catch (RemoteException ex) {
2468                 logRemoteException("notifyRegistered", ex);
2469             }
2470         }
2471 
2472         /**
2473          * Sends the corresponding router an {@link
2474          * android.media.MediaRouter2.RouteCallback#onRoutesUpdated update} for the given {@code
2475          * routes}.
2476          *
2477          * <p>Only the routes that are visible to the router are sent as part of the update.
2478          */
notifyRoutesUpdated(List<MediaRoute2Info> routes)2479         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
2480             try {
2481                 mRouter.notifyRoutesUpdated(getVisibleRoutes(routes));
2482             } catch (RemoteException ex) {
2483                 logRemoteException("notifyRoutesUpdated", ex);
2484             }
2485         }
2486 
notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo)2487         public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
2488             try {
2489                 mRouter.notifySessionCreated(
2490                         requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
2491             } catch (RemoteException ex) {
2492                 logRemoteException("notifySessionCreated", ex);
2493             }
2494         }
2495 
2496         /**
2497          * Notifies the corresponding router of a request failure.
2498          *
2499          * @param requestId The id of the request that failed.
2500          */
notifySessionCreationFailed(int requestId)2501         public void notifySessionCreationFailed(int requestId) {
2502             try {
2503                 mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null);
2504             } catch (RemoteException ex) {
2505                 logRemoteException("notifySessionCreationFailed", ex);
2506             }
2507         }
2508 
2509         /**
2510          * Notifies the corresponding router of the release of the given {@link RoutingSessionInfo}.
2511          */
notifySessionReleased(RoutingSessionInfo sessionInfo)2512         public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
2513             try {
2514                 mRouter.notifySessionReleased(sessionInfo);
2515             } catch (RemoteException ex) {
2516                 logRemoteException("notifySessionReleased", ex);
2517             }
2518         }
2519 
notifyDeviceSuggestionsUpdated( String suggestingPackageName, List<SuggestedDeviceInfo> suggestedDeviceInfo)2520         public void notifyDeviceSuggestionsUpdated(
2521                 String suggestingPackageName, List<SuggestedDeviceInfo> suggestedDeviceInfo) {
2522             try {
2523                 mRouter.notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo);
2524             } catch (RemoteException ex) {
2525                 logRemoteException("notifyDeviceSuggestionsUpdated", ex);
2526             }
2527         }
2528 
2529         /**
2530          * Sends the corresponding router a {@link RoutingSessionInfo session} creation request,
2531          * with the given {@link MediaRoute2Info} as the initial member.
2532          *
2533          * <p>Must be called on the thread of the corresponding {@link UserHandler}.
2534          *
2535          * @param managerRecord The record of the manager that made the request.
2536          * @param uniqueRequestId The id of the request.
2537          * @param oldSession The session from which the transfer originated.
2538          * @param route The initial route member of the session to create.
2539          */
requestCreateSessionByManager( ManagerRecord managerRecord, long uniqueRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route)2540         public void requestCreateSessionByManager(
2541                 ManagerRecord managerRecord,
2542                 long uniqueRequestId,
2543                 RoutingSessionInfo oldSession,
2544                 MediaRoute2Info route) {
2545             try {
2546                 if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
2547                     // The router lacks permission to modify system routing, so we hide system
2548                     // route info from them.
2549                     route = mUserRecord.mHandler.getSystemProvider().getDefaultRoute();
2550                 }
2551                 mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
2552             } catch (RemoteException ex) {
2553                 logRemoteException("requestCreateSessionByManager", ex);
2554                 managerRecord.notifyRequestFailed(
2555                         toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
2556             }
2557         }
2558 
2559         /**
2560          * Sends the corresponding router an update for the given session.
2561          *
2562          * <p>Note: These updates are not directly visible to the app.
2563          */
notifySessionInfoChanged(RoutingSessionInfo sessionInfo)2564         public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
2565             try {
2566                 mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
2567             } catch (RemoteException ex) {
2568                 logRemoteException("notifySessionInfoChanged", ex);
2569             }
2570         }
2571 
maybeClearTransferInitiatorIdentity( @onNull RoutingSessionInfo sessionInfo)2572         private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
2573                 @NonNull RoutingSessionInfo sessionInfo) {
2574             UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
2575             String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
2576 
2577             if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
2578                     || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
2579                 return new RoutingSessionInfo.Builder(sessionInfo)
2580                         .setTransferInitiator(null, null)
2581                         .build();
2582             }
2583 
2584             return sessionInfo;
2585         }
2586 
2587         /**
2588          * Returns a filtered copy of {@code routes} that contains only the routes that are visible
2589          * to this RouterRecord.
2590          */
getVisibleRoutes(@onNull List<MediaRoute2Info> routes)2591         private List<MediaRoute2Info> getVisibleRoutes(@NonNull List<MediaRoute2Info> routes) {
2592             List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
2593             for (MediaRoute2Info route : routes) {
2594                 if (route.isVisibleTo(mPackageName) && hasPermissionsToSeeRoute(route)) {
2595                     filteredRoutes.add(route);
2596                 }
2597             }
2598             return filteredRoutes;
2599         }
2600 
2601         /**
2602          * @return whether this RouterRecord has the required permissions to see the given route.
2603          */
hasPermissionsToSeeRoute(MediaRoute2Info route)2604         private boolean hasPermissionsToSeeRoute(MediaRoute2Info route) {
2605             if (!Flags.enableRouteVisibilityControlApi()) {
2606                 return true;
2607             }
2608             List<Set<String>> permissionSets = route.getRequiredPermissions();
2609             if (permissionSets.isEmpty()) {
2610                 return true;
2611             }
2612             for (Set<String> permissionSet : permissionSets) {
2613                 boolean hasAllInSet = true;
2614                 for (String permission : permissionSet) {
2615                     if (mContext.checkPermission(permission, mPid, mUid)
2616                             != PackageManager.PERMISSION_GRANTED) {
2617                         hasAllInSet = false;
2618                         break;
2619                     }
2620                 }
2621                 if (hasAllInSet) {
2622                     return true;
2623                 }
2624             }
2625             return false;
2626         }
2627 
2628         /** Logs a {@link RemoteException} occurred during the execution of {@code operation}. */
logRemoteException(String operation, RemoteException exception)2629         private void logRemoteException(String operation, RemoteException exception) {
2630             String message =
2631                     TextUtils.formatSimple(
2632                             "%s failed for %s due to %s",
2633                             operation, getDebugString(), exception.toString());
2634             Slog.w(TAG, message);
2635         }
2636 
2637         /** Returns a human readable representation of this router record for logging purposes. */
getDebugString()2638         private String getDebugString() {
2639             return TextUtils.formatSimple(
2640                     "Router %s (id=%d,pid=%d,userId=%d,uid=%d)",
2641                     mPackageName, mRouterId, mPid, mUserRecord.mUserId, mUid);
2642         }
2643     }
2644 
2645     final class ManagerRecord implements IBinder.DeathRecipient {
2646         @NonNull public final UserRecord mUserRecord;
2647         @NonNull public final IMediaRouter2Manager mManager;
2648         public final int mOwnerUid;
2649         public final int mOwnerPid;
2650         @NonNull public final String mOwnerPackageName;
2651         public final int mManagerId;
2652         // TODO (b/281072508): Document behaviour around nullability for mTargetPackageName.
2653         @Nullable public final String mTargetPackageName;
2654 
2655         public final boolean mHasMediaRoutingControl;
2656         public final boolean mHasMediaContentControl;
2657         @Nullable public SessionCreationRequest mLastSessionCreationRequest;
2658 
2659         public @ScanningState int mScanningState = SCANNING_STATE_NOT_SCANNING;
2660 
ManagerRecord( @onNull UserRecord userRecord, @NonNull IMediaRouter2Manager manager, int ownerUid, int ownerPid, @NonNull String ownerPackageName, @Nullable String targetPackageName, boolean hasMediaRoutingControl, boolean hasMediaContentControl)2661         ManagerRecord(
2662                 @NonNull UserRecord userRecord,
2663                 @NonNull IMediaRouter2Manager manager,
2664                 int ownerUid,
2665                 int ownerPid,
2666                 @NonNull String ownerPackageName,
2667                 @Nullable String targetPackageName,
2668                 boolean hasMediaRoutingControl,
2669                 boolean hasMediaContentControl) {
2670             mUserRecord = userRecord;
2671             mManager = manager;
2672             mOwnerUid = ownerUid;
2673             mOwnerPid = ownerPid;
2674             mOwnerPackageName = ownerPackageName;
2675             mTargetPackageName = targetPackageName;
2676             mManagerId = mNextRouterOrManagerId.getAndIncrement();
2677             mHasMediaRoutingControl = hasMediaRoutingControl;
2678             mHasMediaContentControl = hasMediaContentControl;
2679         }
2680 
dispose()2681         public void dispose() {
2682             mManager.asBinder().unlinkToDeath(this, 0);
2683         }
2684 
2685         @Override
binderDied()2686         public void binderDied() {
2687             managerDied(this);
2688         }
2689 
dump(@onNull PrintWriter pw, @NonNull String prefix)2690         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2691             pw.println(prefix + "ManagerRecord");
2692 
2693             String indent = prefix + "  ";
2694 
2695             pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
2696             pw.println(indent + "mTargetPackageName=" + mTargetPackageName);
2697             pw.println(indent + "mManagerId=" + mManagerId);
2698             pw.println(indent + "mOwnerUid=" + mOwnerUid);
2699             pw.println(indent + "mOwnerPid=" + mOwnerPid);
2700             pw.println(indent + "mScanningState=" + getScanningStateString(mScanningState));
2701 
2702             if (mLastSessionCreationRequest != null) {
2703                 mLastSessionCreationRequest.dump(pw, indent);
2704             }
2705         }
2706 
2707         /**
2708          * Notifies the corresponding manager of a request failure.
2709          *
2710          * <p>Must be called on the thread of the corresponding {@link UserHandler}.
2711          *
2712          * @param requestId The id of the request that failed.
2713          * @param reason The reason of the failure. One of
2714          */
notifyRequestFailed(int requestId, int reason)2715         public void notifyRequestFailed(int requestId, int reason) {
2716             try {
2717                 mManager.notifyRequestFailed(requestId, reason);
2718             } catch (RemoteException ex) {
2719                 logRemoteException("notifyRequestFailed", ex);
2720             }
2721         }
2722 
2723         /**
2724          * Notifies the corresponding manager of the availability of the given routes.
2725          *
2726          * @param routes The routes available to the manager that corresponds to this record.
2727          */
notifyRoutesUpdated(List<MediaRoute2Info> routes)2728         public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
2729             try {
2730                 mManager.notifyRoutesUpdated(routes);
2731             } catch (RemoteException ex) {
2732                 logRemoteException("notifyRoutesUpdated", ex);
2733             }
2734         }
2735 
2736         /**
2737          * Notifies the corresponding manager of an update in the given session.
2738          *
2739          * @param sessionInfo The updated session info.
2740          */
notifySessionUpdated(RoutingSessionInfo sessionInfo)2741         public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
2742             try {
2743                 mManager.notifySessionUpdated(sessionInfo);
2744             } catch (RemoteException ex) {
2745                 logRemoteException("notifySessionUpdated", ex);
2746             }
2747         }
2748 
2749         /**
2750          * Notifies the corresponding manager that the given session has been released.
2751          *
2752          * @param sessionInfo The released session info.
2753          */
notifySessionReleased(RoutingSessionInfo sessionInfo)2754         public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
2755             try {
2756                 mManager.notifySessionReleased(sessionInfo);
2757             } catch (RemoteException ex) {
2758                 logRemoteException("notifySessionReleased", ex);
2759             }
2760         }
2761 
logRemoteException(String operation, RemoteException exception)2762         private void logRemoteException(String operation, RemoteException exception) {
2763             String message =
2764                     TextUtils.formatSimple(
2765                             "%s failed for %s due to %s",
2766                             operation, getDebugString(), exception.toString());
2767             Slog.w(TAG, message);
2768         }
2769 
updateScanningState(@canningState int scanningState)2770         private void updateScanningState(@ScanningState int scanningState) {
2771             if (mScanningState == scanningState) {
2772                 return;
2773             }
2774 
2775             mScanningState = scanningState;
2776 
2777             mUserRecord.mHandler.sendMessage(
2778                     obtainMessage(
2779                             UserHandler::updateDiscoveryPreferenceOnHandler, mUserRecord.mHandler));
2780         }
2781 
2782         /** Returns a human readable representation of this manager record for logging purposes. */
getDebugString()2783         public String getDebugString() {
2784             return TextUtils.formatSimple(
2785                     "Manager %s (id=%d,pid=%d,userId=%d,uid=%d,targetPkg=%s)",
2786                     mOwnerPackageName,
2787                     mManagerId,
2788                     mOwnerPid,
2789                     mUserRecord.mUserId,
2790                     mOwnerUid,
2791                     mTargetPackageName);
2792         }
2793     }
2794 
2795     static final class UserHandler extends Handler implements
2796             MediaRoute2ProviderWatcher.Callback,
2797             MediaRoute2Provider.Callback {
2798 
2799         private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
2800         private final UserRecord mUserRecord;
2801         private final MediaRoute2ProviderWatcher mWatcher;
2802 
2803         private final SystemMediaRoute2Provider mSystemProvider;
2804         private final ArrayList<MediaRoute2Provider> mRouteProviders =
2805                 new ArrayList<>();
2806 
2807         private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
2808         private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
2809                 new CopyOnWriteArrayList<>();
2810         private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
2811 
2812         /**
2813          * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
2814          * {@link android.media.MediaRouter2Manager managers}.
2815          *
2816          * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
2817          * that have {@code MODIFY_AUDIO_ROUTING} permission.
2818          *
2819          * <p>This list contains all routes exposed by route providers. This includes routes from
2820          * both system route providers and user route providers.
2821          *
2822          * <p>See {@link #getRouterRecords(boolean hasModifyAudioRoutingPermission)}.
2823          *
2824          * <p>Must be accessed on this handler's thread.
2825          */
2826         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
2827                 new ArrayMap<>();
2828 
2829         /**
2830          * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
2831          *
2832          * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
2833          * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
2834          *
2835          * <p>This list contains all routes exposed by user route providers. It might also include
2836          * the current default route from {@link #mSystemProvider} to expose local route updates
2837          * (e.g. volume changes) to non-privileged routers.
2838          *
2839          * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
2840          *
2841          * <p>Must be accessed on this handler's thread.
2842          */
2843         private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
2844                 new ArrayMap<>();
2845 
2846         private boolean mRunning;
2847 
getSystemProvider()2848         private SystemMediaRoute2Provider getSystemProvider() {
2849             return mSystemProvider;
2850         }
2851 
2852         // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
UserHandler( @onNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord, @NonNull Looper looper)2853         UserHandler(
2854                 @NonNull MediaRouter2ServiceImpl service,
2855                 @NonNull UserRecord userRecord,
2856                 @NonNull Looper looper) {
2857             super(looper, /* callback= */ null, /* async= */ true);
2858             mServiceRef = new WeakReference<>(service);
2859             mUserRecord = userRecord;
2860             mSystemProvider =
2861                     Flags.enableMirroringInMediaRouter2()
2862                             ? SystemMediaRoute2Provider2.create(
2863                                     service.mContext, UserHandle.of(userRecord.mUserId), looper)
2864                             : SystemMediaRoute2Provider.create(
2865                                     service.mContext, UserHandle.of(userRecord.mUserId), looper);
2866             mRouteProviders.add(getSystemProvider());
2867             mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
2868                     this, mUserRecord.mUserId);
2869         }
2870 
init()2871         void init() {
2872             getSystemProvider().setCallback(this);
2873         }
2874 
start()2875         private void start() {
2876             if (!mRunning) {
2877                 mRunning = true;
2878                 getSystemProvider().start();
2879                 mWatcher.start();
2880             }
2881         }
2882 
stop()2883         private void stop() {
2884             if (mRunning) {
2885                 mRunning = false;
2886                 mWatcher.stop(); // also stops all providers
2887                 getSystemProvider().stop();
2888             }
2889         }
2890 
2891         @Override
onAddProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)2892         public void onAddProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
2893             proxy.setCallback(this);
2894             mRouteProviders.add(proxy);
2895             proxy.updateDiscoveryPreference(
2896                     mUserRecord.mActivelyScanningPackages,
2897                     mUserRecord.mCompositeDiscoveryPreference);
2898         }
2899 
2900         @Override
onRemoveProviderService(@onNull MediaRoute2ProviderServiceProxy proxy)2901         public void onRemoveProviderService(@NonNull MediaRoute2ProviderServiceProxy proxy) {
2902             mRouteProviders.remove(proxy);
2903         }
2904 
2905         @Override
onProviderStateChanged(@onNull MediaRoute2Provider provider)2906         public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
2907             sendMessage(PooledLambda.obtainMessage(UserHandler::onProviderStateChangedOnHandler,
2908                     this, provider));
2909         }
2910 
2911         @Override
onSessionCreated(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)2912         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
2913                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
2914             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
2915                     this, provider, uniqueRequestId, sessionInfo));
2916         }
2917 
2918         @Override
onSessionUpdated( @onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo, Set<String> packageNamesWithRoutingSessionOverrides)2919         public void onSessionUpdated(
2920                 @NonNull MediaRoute2Provider provider,
2921                 @NonNull RoutingSessionInfo sessionInfo,
2922                 Set<String> packageNamesWithRoutingSessionOverrides) {
2923             sendMessage(
2924                     PooledLambda.obtainMessage(
2925                             UserHandler::onSessionInfoChangedOnHandler,
2926                             this,
2927                             provider,
2928                             sessionInfo,
2929                             packageNamesWithRoutingSessionOverrides));
2930         }
2931 
2932         @Override
onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)2933         public void onSessionReleased(@NonNull MediaRoute2Provider provider,
2934                 @NonNull RoutingSessionInfo sessionInfo) {
2935             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
2936                     this, provider, sessionInfo));
2937         }
2938 
2939         @Override
onRequestFailed(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)2940         public void onRequestFailed(@NonNull MediaRoute2Provider provider, long uniqueRequestId,
2941                 int reason) {
2942             sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
2943                     this, provider, uniqueRequestId, reason));
2944         }
2945 
2946         @GuardedBy("mLock")
2947         @Nullable
findRouterWithSessionLocked(@onNull String uniqueSessionId)2948         public RouterRecord findRouterWithSessionLocked(@NonNull String uniqueSessionId) {
2949             return mSessionToRouterMap.get(uniqueSessionId);
2950         }
2951 
2952         @Nullable
findManagerWithId(int managerId)2953         public ManagerRecord findManagerWithId(int managerId) {
2954             for (ManagerRecord manager : getManagerRecords()) {
2955                 if (manager.mManagerId == managerId) {
2956                     return manager;
2957                 }
2958             }
2959             return null;
2960         }
2961 
maybeUpdateDiscoveryPreferenceForUid(int uid)2962         public void maybeUpdateDiscoveryPreferenceForUid(int uid) {
2963             MediaRouter2ServiceImpl service = mServiceRef.get();
2964             if (service == null) {
2965                 return;
2966             }
2967             boolean isUidRelevant;
2968             synchronized (service.mLock) {
2969                 isUidRelevant =
2970                         mUserRecord.mRouterRecords.stream().anyMatch(router -> router.mUid == uid)
2971                                 | mUserRecord.mManagerRecords.stream()
2972                                         .anyMatch(manager -> manager.mOwnerUid == uid);
2973             }
2974             if (isUidRelevant) {
2975                 sendMessage(PooledLambda.obtainMessage(
2976                         UserHandler::updateDiscoveryPreferenceOnHandler, this));
2977             }
2978         }
2979 
dump(@onNull PrintWriter pw, @NonNull String prefix)2980         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
2981             pw.println(prefix + "UserHandler");
2982 
2983             String indent = prefix + "  ";
2984             pw.println(indent + "mRunning=" + mRunning);
2985 
2986             getSystemProvider().dump(pw, prefix);
2987             mWatcher.dump(pw, prefix);
2988         }
2989 
onProviderStateChangedOnHandler(@onNull MediaRoute2Provider provider)2990         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
2991             MediaRoute2ProviderInfo newInfo = provider.getProviderInfo();
2992             int providerInfoIndex =
2993                     indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
2994             MediaRoute2ProviderInfo oldInfo =
2995                     providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
2996 
2997             if (oldInfo == newInfo) {
2998                 // Nothing to do.
2999                 return;
3000             }
3001 
3002             Collection<MediaRoute2Info> newRoutes;
3003             Set<String> newRouteIds;
3004             if (newInfo != null) {
3005                 // Adding or updating a provider.
3006                 newRoutes = newInfo.getRoutes();
3007                 newRouteIds =
3008                         newRoutes.stream().map(MediaRoute2Info::getId).collect(Collectors.toSet());
3009                 if (providerInfoIndex >= 0) {
3010                     mLastProviderInfos.set(providerInfoIndex, newInfo);
3011                 } else {
3012                     mLastProviderInfos.add(newInfo);
3013                 }
3014             } else /* newInfo == null */ {
3015                 // Removing a provider.
3016                 mLastProviderInfos.remove(oldInfo);
3017                 newRouteIds = Collections.emptySet();
3018                 newRoutes = Collections.emptySet();
3019             }
3020 
3021             if (Flags.enableMirroringInMediaRouter2()
3022                     && provider instanceof MediaRoute2ProviderServiceProxy proxyProvider) {
3023                 // We notify the system provider of service updates, so that it can update the
3024                 // system routing session by adding them as transferable routes. And we remove those
3025                 // that don't support remote routing.
3026                 mSystemProvider.updateSystemMediaRoutesFromProxy(proxyProvider);
3027                 newRoutes.removeIf(it -> !it.supportsRemoteRouting());
3028             }
3029 
3030             // Add new routes to the maps.
3031             ArrayList<MediaRoute2Info> addedRoutes = new ArrayList<>();
3032             boolean hasAddedOrModifiedRoutes = false;
3033             for (MediaRoute2Info newRouteInfo : newRoutes) {
3034                 if (!newRouteInfo.isValid()) {
3035                     Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
3036                             + newRouteInfo);
3037                     continue;
3038                 }
3039                 if (!provider.mIsSystemRouteProvider) {
3040                     mLastNotifiedRoutesToNonPrivilegedRouters.put(
3041                             newRouteInfo.getId(), newRouteInfo);
3042                 }
3043                 MediaRoute2Info oldRouteInfo =
3044                         mLastNotifiedRoutesToPrivilegedRouters.put(
3045                                 newRouteInfo.getId(), newRouteInfo);
3046                 hasAddedOrModifiedRoutes |= !newRouteInfo.equals(oldRouteInfo);
3047                 if (oldRouteInfo == null) {
3048                     addedRoutes.add(newRouteInfo);
3049                 }
3050             }
3051 
3052             // Remove stale routes from the maps.
3053             ArrayList<MediaRoute2Info> removedRoutes = new ArrayList<>();
3054             Collection<MediaRoute2Info> oldRoutes =
3055                     oldInfo == null ? Collections.emptyList() : oldInfo.getRoutes();
3056             boolean hasRemovedRoutes = false;
3057             for (MediaRoute2Info oldRoute : oldRoutes) {
3058                 String oldRouteId = oldRoute.getId();
3059                 if (!newRouteIds.contains(oldRouteId)) {
3060                     hasRemovedRoutes = true;
3061                     mLastNotifiedRoutesToPrivilegedRouters.remove(oldRouteId);
3062                     mLastNotifiedRoutesToNonPrivilegedRouters.remove(oldRouteId);
3063                     removedRoutes.add(oldRoute);
3064                 }
3065             }
3066 
3067             if (!addedRoutes.isEmpty()) {
3068                 // If routes were added, newInfo cannot be null.
3069                 Slog.i(
3070                         TAG,
3071                         toLoggingMessage(
3072                                 /* source= */ "addProviderRoutes",
3073                                 newInfo.getUniqueId(),
3074                                 addedRoutes));
3075             }
3076             if (!removedRoutes.isEmpty()) {
3077                 // If routes were removed, oldInfo cannot be null.
3078                 Slog.i(TAG,
3079                         toLoggingMessage(
3080                                 /* source= */ "removeProviderRoutes",
3081                                 oldInfo.getUniqueId(),
3082                                 removedRoutes));
3083             }
3084 
3085             dispatchUpdatesOnHandler(
3086                     hasAddedOrModifiedRoutes,
3087                     hasRemovedRoutes,
3088                     provider.mIsSystemRouteProvider,
3089                     getSystemProvider().getDefaultRoute());
3090         }
3091 
getPackageNameFromNullableRecord( @ullable RouterRecord routerRecord)3092         private static String getPackageNameFromNullableRecord(
3093                 @Nullable RouterRecord routerRecord) {
3094             return routerRecord != null ? routerRecord.mPackageName : "<null router record>";
3095         }
3096 
toLoggingMessage( String source, String providerId, ArrayList<MediaRoute2Info> routes)3097         private static String toLoggingMessage(
3098                 String source, String providerId, ArrayList<MediaRoute2Info> routes) {
3099             String routesString =
3100                     routes.stream()
3101                             .map(it -> String.format("%s | %s", it.getOriginalId(), it.getName()))
3102                             .collect(Collectors.joining(/* delimiter= */ ", "));
3103             return TextUtils.formatSimple("%s | provider: %s, routes: [%s]",
3104                     source, providerId, routesString);
3105         }
3106 
3107         /** Notifies the given manager of the current routes. */
dispatchRoutesToManagerOnHandler(ManagerRecord managerRecord)3108         public void dispatchRoutesToManagerOnHandler(ManagerRecord managerRecord) {
3109             List<MediaRoute2Info> routes =
3110                     mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList();
3111             managerRecord.notifyRoutesUpdated(routes);
3112         }
3113 
3114         /**
3115          * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
3116          * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
3117          * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
3118          * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
3119          * were made.
3120          *
3121          * @param hasAddedOrModifiedRoutes whether routes were added or modified.
3122          * @param hasRemovedRoutes whether routes were removed.
3123          * @param isSystemProvider whether the latest update was caused by a system provider.
3124          * @param defaultRoute the current default route in {@link #mSystemProvider}.
3125          */
dispatchUpdatesOnHandler( boolean hasAddedOrModifiedRoutes, boolean hasRemovedRoutes, boolean isSystemProvider, MediaRoute2Info defaultRoute)3126         private void dispatchUpdatesOnHandler(
3127                 boolean hasAddedOrModifiedRoutes,
3128                 boolean hasRemovedRoutes,
3129                 boolean isSystemProvider,
3130                 MediaRoute2Info defaultRoute) {
3131 
3132             // Ignore if no changes.
3133             if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
3134                 return;
3135             }
3136             List<RouterRecord> routerRecordsWithSystemRoutingPermission =
3137                     getRouterRecords(/* hasSystemRoutingPermission= */ true);
3138             List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
3139                     getRouterRecords(/* hasSystemRoutingPermission= */ false);
3140             List<ManagerRecord> managers = getManagerRecords();
3141 
3142             // Managers receive all provider updates with all routes.
3143             List<MediaRoute2Info> routesForPrivilegedRouters =
3144                     mLastNotifiedRoutesToPrivilegedRouters.values().stream().toList();
3145             for (ManagerRecord manager : managers) {
3146                 manager.notifyRoutesUpdated(routesForPrivilegedRouters);
3147             }
3148 
3149             // Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
3150             // {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
3151             // with all routes.
3152             notifyRoutesUpdatedToRouterRecords(
3153                     routerRecordsWithSystemRoutingPermission, routesForPrivilegedRouters);
3154 
3155             if (!isSystemProvider) {
3156                 // Regular routers receive updates from all non-system providers with all non-system
3157                 // routes.
3158                 notifyRoutesUpdatedToRouterRecords(
3159                         routerRecordsWithoutSystemRoutingPermission,
3160                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
3161             } else if (hasAddedOrModifiedRoutes) {
3162                 // On system provider updates, routers without system routing access
3163                 // receive the updated default route. This is the only system route they should
3164                 // receive.
3165                 mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
3166                 notifyRoutesUpdatedToRouterRecords(
3167                         routerRecordsWithoutSystemRoutingPermission,
3168                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
3169             }
3170         }
3171 
3172         /**
3173          * Returns the index of the first element in {@code lastProviderInfos} that matches the
3174          * specified unique id.
3175          *
3176          * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
3177          * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
3178          * @return index of found element, or -1 if not found.
3179          */
indexOfRouteProviderInfoByUniqueId( @onNull String uniqueId, @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos)3180         private static int indexOfRouteProviderInfoByUniqueId(
3181                 @NonNull String uniqueId,
3182                 @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
3183             for (int i = 0; i < lastProviderInfos.size(); i++) {
3184                 MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
3185                 if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
3186                     return i;
3187                 }
3188             }
3189             return -1;
3190         }
3191 
requestCreateSessionWithRouter2OnHandler( long uniqueRequestId, long managerRequestId, @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints)3192         private void requestCreateSessionWithRouter2OnHandler(
3193                 long uniqueRequestId,
3194                 long managerRequestId,
3195                 @NonNull RouterRecord routerRecord,
3196                 @NonNull RoutingSessionInfo oldSession,
3197                 @NonNull MediaRoute2Info route,
3198                 @Nullable Bundle sessionHints) {
3199 
3200             final MediaRoute2Provider provider = findProvider(route.getProviderId());
3201             if (provider == null) {
3202                 Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session "
3203                         + "creation request since no provider found for given route=" + route);
3204                 routerRecord.notifySessionCreationFailed(toOriginalRequestId(uniqueRequestId));
3205                 return;
3206             }
3207 
3208             SessionCreationRequest request =
3209                     new SessionCreationRequest(routerRecord, uniqueRequestId,
3210                             managerRequestId, oldSession, route);
3211             mSessionCreationRequests.add(request);
3212 
3213             int transferReason =
3214                     managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE
3215                             ? RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST
3216                             : RoutingSessionInfo.TRANSFER_REASON_APP;
3217 
3218             provider.requestCreateSession(
3219                     uniqueRequestId,
3220                     routerRecord.mPackageName,
3221                     route.getOriginalId(),
3222                     sessionHints,
3223                     transferReason,
3224                     UserHandle.of(routerRecord.mUserRecord.mUserId),
3225                     routerRecord.mPackageName);
3226         }
3227 
3228         // routerRecord can be null if the session is system's or RCN.
selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)3229         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
3230                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
3231             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
3232                     "selecting", uniqueRequestId)) {
3233                 return;
3234             }
3235 
3236             final String providerId = route.getProviderId();
3237             final MediaRoute2Provider provider = findProvider(providerId);
3238             if (provider == null) {
3239                 return;
3240             }
3241             provider.selectRoute(
3242                     uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
3243 
3244             // Log the success result.
3245             mMediaRouterMetricLogger.logRequestResult(
3246                     uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
3247         }
3248 
3249         // routerRecord can be null if the session is system's or RCN.
deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route)3250         private void deselectRouteOnHandler(long uniqueRequestId,
3251                 @Nullable RouterRecord routerRecord,
3252                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
3253             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
3254                     "deselecting", uniqueRequestId)) {
3255                 return;
3256             }
3257 
3258             final String providerId = route.getProviderId();
3259             final MediaRoute2Provider provider = findProvider(providerId);
3260             if (provider == null) {
3261                 return;
3262             }
3263 
3264             provider.deselectRoute(
3265                     uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
3266 
3267             // Log the success result.
3268             mMediaRouterMetricLogger.logRequestResult(
3269                     uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
3270         }
3271 
3272         // routerRecord can be null if the session is system's or RCN.
transferToRouteOnHandler( long uniqueRequestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @RoutingSessionInfo.TransferReason int transferReason)3273         private void transferToRouteOnHandler(
3274                 long uniqueRequestId,
3275                 @NonNull UserHandle transferInitiatorUserHandle,
3276                 @NonNull String transferInitiatorPackageName,
3277                 @Nullable RouterRecord routerRecord,
3278                 @NonNull String uniqueSessionId,
3279                 @NonNull MediaRoute2Info route,
3280                 @RoutingSessionInfo.TransferReason int transferReason) {
3281             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
3282                     "transferring to", uniqueRequestId)) {
3283                 return;
3284             }
3285 
3286             final String providerId = route.getProviderId();
3287             final MediaRoute2Provider provider = findProvider(providerId);
3288             if (provider == null) {
3289                 Slog.w(
3290                         TAG,
3291                         "Ignoring transferToRoute due to lack of matching provider for target: "
3292                                 + route);
3293                 return;
3294             }
3295             provider.transferToRoute(
3296                     uniqueRequestId,
3297                     transferInitiatorUserHandle,
3298                     transferInitiatorPackageName,
3299                     getOriginalId(uniqueSessionId),
3300                     route.getOriginalId(),
3301                     transferReason);
3302 
3303             // Log the success result.
3304             mMediaRouterMetricLogger.logRequestResult(
3305                     uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
3306         }
3307 
3308         // routerRecord is null if and only if the session is created without the request, which
3309         // includes the system's session and RCN cases.
checkArgumentsForSessionControl(@ullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description, long uniqueRequestId)3310         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
3311                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
3312                 @NonNull String description, long uniqueRequestId) {
3313             final String providerId = route.getProviderId();
3314             final MediaRoute2Provider provider = findProvider(providerId);
3315             if (provider == null) {
3316                 Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
3317                         + "given route=" + route);
3318                 mMediaRouterMetricLogger.logRequestResult(
3319                         uniqueRequestId,
3320                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND);
3321                 return false;
3322             }
3323 
3324             // Bypass checking router if it's the system session (routerRecord should be null)
3325             if (TextUtils.equals(
3326                     getProviderId(uniqueSessionId), getSystemProvider().getUniqueId())) {
3327                 return true;
3328             }
3329 
3330             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
3331             if (matchingRecord != routerRecord) {
3332                 Slog.w(
3333                         TAG,
3334                         "Ignoring "
3335                                 + description
3336                                 + " route from non-matching router."
3337                                 + " routerRecordPackageName="
3338                                 + getPackageNameFromNullableRecord(routerRecord)
3339                                 + " matchingRecordPackageName="
3340                                 + getPackageNameFromNullableRecord(matchingRecord)
3341                                 + " route="
3342                                 + route);
3343                 mMediaRouterMetricLogger.logRequestResult(
3344                         uniqueRequestId,
3345                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
3346                 return false;
3347             }
3348 
3349             final String sessionId = getOriginalId(uniqueSessionId);
3350             if (sessionId == null) {
3351                 Slog.w(TAG, "Failed to get original session id from unique session id. "
3352                         + "uniqueSessionId=" + uniqueSessionId);
3353                 mMediaRouterMetricLogger.logRequestResult(
3354                         uniqueRequestId,
3355                         MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
3356                 return false;
3357             }
3358 
3359             return true;
3360         }
3361 
setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route, int volume)3362         private void setRouteVolumeOnHandler(long uniqueRequestId, @NonNull MediaRoute2Info route,
3363                 int volume) {
3364             final MediaRoute2Provider provider = findProvider(route.getProviderId());
3365             if (provider == null) {
3366                 Slog.w(TAG, "setRouteVolumeOnHandler: Couldn't find provider for route=" + route);
3367                 return;
3368             }
3369             provider.setRouteVolume(uniqueRequestId, route.getOriginalId(), volume);
3370         }
3371 
setSessionVolumeOnHandler(long uniqueRequestId, @NonNull String uniqueSessionId, int volume)3372         private void setSessionVolumeOnHandler(long uniqueRequestId,
3373                 @NonNull String uniqueSessionId, int volume) {
3374             final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
3375             if (provider == null) {
3376                 Slog.w(TAG, "setSessionVolumeOnHandler: Couldn't find provider for session id="
3377                         + uniqueSessionId);
3378                 return;
3379             }
3380             provider.setSessionVolume(uniqueRequestId, getOriginalId(uniqueSessionId), volume);
3381         }
3382 
releaseSessionOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId)3383         private void releaseSessionOnHandler(long uniqueRequestId,
3384                 @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
3385             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
3386             if (matchingRecord != routerRecord) {
3387                 Slog.w(
3388                         TAG,
3389                         "Ignoring releasing session from non-matching router."
3390                                 + " routerRecordPackageName="
3391                                 + getPackageNameFromNullableRecord(routerRecord)
3392                                 + " matchingRecordPackageName="
3393                                 + getPackageNameFromNullableRecord(matchingRecord)
3394                                 + " uniqueSessionId="
3395                                 + uniqueSessionId);
3396                 return;
3397             }
3398 
3399             final String providerId = getProviderId(uniqueSessionId);
3400             if (providerId == null) {
3401                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
3402                         + "uniqueSessionId=" + uniqueSessionId);
3403                 return;
3404             }
3405 
3406             final String sessionId = getOriginalId(uniqueSessionId);
3407             if (sessionId == null) {
3408                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
3409                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
3410                 return;
3411             }
3412 
3413             final MediaRoute2Provider provider = findProvider(providerId);
3414             if (provider == null) {
3415                 Slog.w(TAG, "Ignoring releasing session since no provider found for given "
3416                         + "providerId=" + providerId);
3417                 return;
3418             }
3419 
3420             provider.releaseSession(uniqueRequestId, sessionId);
3421         }
3422 
onSessionCreatedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo)3423         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
3424                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
3425             SessionCreationRequest matchingRequest = null;
3426 
3427             for (SessionCreationRequest request : mSessionCreationRequests) {
3428                 if (request.mUniqueRequestId == uniqueRequestId
3429                         && TextUtils.equals(
3430                         request.mRoute.getProviderId(), provider.getUniqueId())) {
3431                     matchingRequest = request;
3432                     break;
3433                 }
3434             }
3435 
3436             long managerRequestId = (matchingRequest == null)
3437                     ? MediaRoute2ProviderService.REQUEST_ID_NONE
3438                     : matchingRequest.mManagerRequestId;
3439             notifySessionCreatedToManagers(managerRequestId, sessionInfo);
3440 
3441             if (matchingRequest == null) {
3442                 Slog.w(TAG, "Ignoring session creation result for unknown request. "
3443                         + "uniqueRequestId=" + uniqueRequestId + ", sessionInfo=" + sessionInfo);
3444                 return;
3445             }
3446 
3447             mSessionCreationRequests.remove(matchingRequest);
3448             // Not to show old session
3449             MediaRoute2Provider oldProvider =
3450                     findProvider(matchingRequest.mOldSession.getProviderId());
3451             if (oldProvider != null) {
3452                 oldProvider.prepareReleaseSession(matchingRequest.mOldSession.getId());
3453             } else {
3454                 Slog.w(TAG, "onSessionCreatedOnHandler: Can't find provider for an old session. "
3455                         + "session=" + matchingRequest.mOldSession);
3456             }
3457 
3458             mSessionToRouterMap.put(sessionInfo.getId(), matchingRequest.mRouterRecord);
3459             if (sessionInfo.isSystemSession()
3460                     && !matchingRequest.mRouterRecord.hasSystemRoutingPermission()) {
3461                 // The router lacks permission to modify system routing, so we hide system routing
3462                 // session info from them.
3463                 sessionInfo = getSystemProvider().getDefaultSessionInfo();
3464             }
3465             matchingRequest.mRouterRecord.notifySessionCreated(
3466                     toOriginalRequestId(uniqueRequestId), sessionInfo);
3467 
3468             // Log the success result.
3469             mMediaRouterMetricLogger.logRequestResult(
3470                     uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
3471         }
3472 
3473         /**
3474          * Implementation of {@link MediaRoute2Provider.Callback#onSessionUpdated}.
3475          *
3476          * <p>Must run on the thread that corresponds to this {@link UserHandler}.
3477          */
onSessionInfoChangedOnHandler( @onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo, Set<String> packageNamesWithRoutingSessionOverrides)3478         private void onSessionInfoChangedOnHandler(
3479                 @NonNull MediaRoute2Provider provider,
3480                 @NonNull RoutingSessionInfo sessionInfo,
3481                 Set<String> packageNamesWithRoutingSessionOverrides) {
3482             List<ManagerRecord> managers = getManagerRecords();
3483             for (ManagerRecord manager : managers) {
3484                 if (Flags.enableMirroringInMediaRouter2()) {
3485                     String targetPackageName = manager.mTargetPackageName;
3486                     boolean skipDueToOverride =
3487                             targetPackageName != null
3488                                     && packageNamesWithRoutingSessionOverrides.contains(
3489                                             targetPackageName);
3490                     boolean sessionIsForTargetPackage =
3491                             TextUtils.isEmpty(sessionInfo.getClientPackageName()) // is global.
3492                                     || TextUtils.equals(
3493                                             targetPackageName, sessionInfo.getClientPackageName());
3494                     if (skipDueToOverride || !sessionIsForTargetPackage) {
3495                         continue;
3496                     }
3497                 }
3498                 manager.notifySessionUpdated(sessionInfo);
3499             }
3500 
3501             // For system provider, notify all routers.
3502             if (provider == getSystemProvider()) {
3503                 if (mServiceRef.get() == null) {
3504                     return;
3505                 }
3506                 notifySessionInfoChangedToRouters(getRouterRecords(true), sessionInfo);
3507                 notifySessionInfoChangedToRouters(
3508                         getRouterRecords(false), getSystemProvider().getDefaultSessionInfo());
3509                 return;
3510             }
3511 
3512             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
3513             if (routerRecord == null) {
3514                 Slog.w(TAG, "onSessionInfoChangedOnHandler: No matching router found for session="
3515                         + sessionInfo);
3516                 return;
3517             }
3518             notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
3519         }
3520 
onSessionReleasedOnHandler(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)3521         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
3522                 @NonNull RoutingSessionInfo sessionInfo) {
3523             List<ManagerRecord> managers = getManagerRecords();
3524             for (ManagerRecord manager : managers) {
3525                 manager.notifySessionReleased(sessionInfo);
3526             }
3527 
3528             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
3529             if (routerRecord == null) {
3530                 Slog.w(TAG, "onSessionReleasedOnHandler: No matching router found for session="
3531                         + sessionInfo);
3532                 return;
3533             }
3534             routerRecord.notifySessionReleased(sessionInfo);
3535         }
3536 
onRequestFailedOnHandler(@onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)3537         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
3538                 long uniqueRequestId, int reason) {
3539             if (handleSessionCreationRequestFailed(provider, uniqueRequestId, reason)) {
3540                 Slog.w(
3541                         TAG,
3542                         TextUtils.formatSimple(
3543                                 "onRequestFailedOnHandler | Finished handling session creation"
3544                                     + " request failed for provider: %s, uniqueRequestId: %d,"
3545                                     + " reason: %d",
3546                                 provider.getUniqueId(), uniqueRequestId, reason));
3547                 return;
3548             }
3549 
3550             final int requesterId = toRequesterId(uniqueRequestId);
3551             ManagerRecord manager = findManagerWithId(requesterId);
3552             if (manager != null) {
3553                 manager.notifyRequestFailed(toOriginalRequestId(uniqueRequestId), reason);
3554             }
3555 
3556             // Currently, only manager records can get notified of failures.
3557             // TODO(b/282936553): Notify regular routers of request failures.
3558 
3559             // Log the request result.
3560             mMediaRouterMetricLogger.logRequestResult(
3561                     uniqueRequestId, MediaRouterMetricLogger.convertResultFromReason(reason));
3562         }
3563 
handleSessionCreationRequestFailed( @onNull MediaRoute2Provider provider, long uniqueRequestId, int reason)3564         private boolean handleSessionCreationRequestFailed(
3565                 @NonNull MediaRoute2Provider provider, long uniqueRequestId, int reason) {
3566             // Check whether the failure is about creating a session
3567             SessionCreationRequest matchingRequest = null;
3568             for (SessionCreationRequest request : mSessionCreationRequests) {
3569                 if (request.mUniqueRequestId == uniqueRequestId && TextUtils.equals(
3570                         request.mRoute.getProviderId(), provider.getUniqueId())) {
3571                     matchingRequest = request;
3572                     break;
3573                 }
3574             }
3575 
3576             if (matchingRequest == null) {
3577                 // The failure is not about creating a session.
3578                 Slog.w(
3579                         TAG,
3580                         TextUtils.formatSimple(
3581                                 "handleSessionCreationRequestFailed | No matching request found for"
3582                                     + " provider: %s, uniqueRequestId: %d, reason: %d",
3583                                 provider.getUniqueId(), uniqueRequestId, reason));
3584                 return false;
3585             }
3586 
3587             mSessionCreationRequests.remove(matchingRequest);
3588 
3589             // Notify the requester about the failure.
3590             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
3591             if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
3592                 matchingRequest.mRouterRecord.notifySessionCreationFailed(
3593                         toOriginalRequestId(uniqueRequestId));
3594             } else {
3595                 final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
3596                 ManagerRecord manager = findManagerWithId(requesterId);
3597                 if (manager != null) {
3598                     manager.notifyRequestFailed(
3599                             toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
3600                 }
3601             }
3602             return true;
3603         }
3604 
getRouterRecords()3605         private List<RouterRecord> getRouterRecords() {
3606             MediaRouter2ServiceImpl service = mServiceRef.get();
3607             if (service == null) {
3608                 return Collections.emptyList();
3609             }
3610             synchronized (service.mLock) {
3611                 return new ArrayList<>(mUserRecord.mRouterRecords);
3612             }
3613         }
3614 
getRouterRecords(boolean hasSystemRoutingPermission)3615         private List<RouterRecord> getRouterRecords(boolean hasSystemRoutingPermission) {
3616             MediaRouter2ServiceImpl service = mServiceRef.get();
3617             List<RouterRecord> routerRecords = new ArrayList<>();
3618             if (service == null) {
3619                 return routerRecords;
3620             }
3621             synchronized (service.mLock) {
3622                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
3623                     if (hasSystemRoutingPermission
3624                             == routerRecord.hasSystemRoutingPermission()) {
3625                         routerRecords.add(routerRecord);
3626                     }
3627                 }
3628                 return routerRecords;
3629             }
3630         }
3631 
getManagerRecords()3632         private List<ManagerRecord> getManagerRecords() {
3633             MediaRouter2ServiceImpl service = mServiceRef.get();
3634             if (service == null) {
3635                 return Collections.emptyList();
3636             }
3637             synchronized (service.mLock) {
3638                 return new ArrayList<>(mUserRecord.mManagerRecords);
3639             }
3640         }
3641 
notifyRouterRegistered(@onNull RouterRecord routerRecord)3642         private void notifyRouterRegistered(@NonNull RouterRecord routerRecord) {
3643             List<MediaRoute2Info> currentRoutes = new ArrayList<>();
3644 
3645             MediaRoute2ProviderInfo systemProviderInfo = null;
3646             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
3647                 // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
3648                 if (TextUtils.equals(
3649                         providerInfo.getUniqueId(), getSystemProvider().getUniqueId())) {
3650                     // Adding routes from system provider will be handled below, so skip it here.
3651                     systemProviderInfo = providerInfo;
3652                     continue;
3653                 }
3654                 currentRoutes.addAll(providerInfo.getRoutes());
3655             }
3656 
3657             RoutingSessionInfo currentSystemSessionInfo;
3658             if (routerRecord.hasSystemRoutingPermission()) {
3659                 if (systemProviderInfo != null) {
3660                     currentRoutes.addAll(systemProviderInfo.getRoutes());
3661                 } else {
3662                     // This shouldn't happen.
3663                     Slog.wtf(TAG, "System route provider not found.");
3664                 }
3665                 currentSystemSessionInfo = getSystemProvider().getSessionInfos().get(0);
3666             } else {
3667                 currentRoutes.add(getSystemProvider().getDefaultRoute());
3668                 currentSystemSessionInfo = getSystemProvider().getDefaultSessionInfo();
3669             }
3670 
3671             if (!currentRoutes.isEmpty()) {
3672                 routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
3673             }
3674         }
3675 
notifyRoutesUpdatedToRouterRecords( @onNull List<RouterRecord> routerRecords, @NonNull List<MediaRoute2Info> routes)3676         private static void notifyRoutesUpdatedToRouterRecords(
3677                 @NonNull List<RouterRecord> routerRecords,
3678                 @NonNull List<MediaRoute2Info> routes) {
3679             for (RouterRecord routerRecord : routerRecords) {
3680                 routerRecord.notifyRoutesUpdated(routes);
3681             }
3682         }
3683 
notifySessionInfoChangedToRouters( @onNull List<RouterRecord> routerRecords, @NonNull RoutingSessionInfo sessionInfo)3684         private void notifySessionInfoChangedToRouters(
3685                 @NonNull List<RouterRecord> routerRecords,
3686                 @NonNull RoutingSessionInfo sessionInfo) {
3687             for (RouterRecord routerRecord : routerRecords) {
3688                 routerRecord.notifySessionInfoChanged(sessionInfo);
3689             }
3690         }
3691 
notifySessionCreatedToManagers( long managerRequestId, @NonNull RoutingSessionInfo session)3692         private void notifySessionCreatedToManagers(
3693                 long managerRequestId, @NonNull RoutingSessionInfo session) {
3694             int requesterId = toRequesterId(managerRequestId);
3695             int originalRequestId = toOriginalRequestId(managerRequestId);
3696 
3697             for (ManagerRecord manager : getManagerRecords()) {
3698                 try {
3699                     manager.mManager.notifySessionCreated(
3700                             ((manager.mManagerId == requesterId) ? originalRequestId :
3701                                     MediaRouter2Manager.REQUEST_ID_NONE), session);
3702                 } catch (RemoteException ex) {
3703                     Slog.w(TAG, "notifySessionCreatedToManagers: "
3704                             + "Failed to notify. Manager probably died.", ex);
3705                 }
3706             }
3707         }
3708 
notifyDiscoveryPreferenceChangedToManager(@onNull RouterRecord routerRecord, @NonNull IMediaRouter2Manager manager)3709         private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord,
3710                 @NonNull IMediaRouter2Manager manager) {
3711             try {
3712                 manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName,
3713                         routerRecord.mDiscoveryPreference);
3714             } catch (RemoteException ex) {
3715                 Slog.w(TAG, "Failed to notify preferred features changed."
3716                         + " Manager probably died.", ex);
3717             }
3718         }
3719 
notifyDiscoveryPreferenceChangedToManagers(@onNull String routerPackageName, @Nullable RouteDiscoveryPreference discoveryPreference)3720         private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName,
3721                 @Nullable RouteDiscoveryPreference discoveryPreference) {
3722             MediaRouter2ServiceImpl service = mServiceRef.get();
3723             if (service == null) {
3724                 return;
3725             }
3726             List<IMediaRouter2Manager> managers = new ArrayList<>();
3727             synchronized (service.mLock) {
3728                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3729                     managers.add(managerRecord.mManager);
3730                 }
3731             }
3732             for (IMediaRouter2Manager manager : managers) {
3733                 try {
3734                     manager.notifyDiscoveryPreferenceChanged(routerPackageName,
3735                             discoveryPreference);
3736                 } catch (RemoteException ex) {
3737                     Slog.w(TAG, "Failed to notify preferred features changed."
3738                             + " Manager probably died.", ex);
3739                 }
3740             }
3741         }
3742 
notifyRouteListingPreferenceChangeToManagers( String routerPackageName, @Nullable RouteListingPreference routeListingPreference)3743         private void notifyRouteListingPreferenceChangeToManagers(
3744                 String routerPackageName, @Nullable RouteListingPreference routeListingPreference) {
3745             MediaRouter2ServiceImpl service = mServiceRef.get();
3746             if (service == null) {
3747                 return;
3748             }
3749             List<IMediaRouter2Manager> managers = new ArrayList<>();
3750             synchronized (service.mLock) {
3751                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3752                     managers.add(managerRecord.mManager);
3753                 }
3754             }
3755             for (IMediaRouter2Manager manager : managers) {
3756                 try {
3757                     manager.notifyRouteListingPreferenceChange(
3758                             routerPackageName, routeListingPreference);
3759                 } catch (RemoteException ex) {
3760                     Slog.w(
3761                             TAG,
3762                             "Failed to notify preferred features changed."
3763                                     + " Manager probably died.",
3764                             ex);
3765                 }
3766             }
3767             // TODO(b/238178508): In order to support privileged media router instances, we also
3768             //    need to update routers other than the one making the update.
3769         }
3770 
notifyDeviceSuggestionsUpdatedOnHandler( String routerPackageName, String suggestingPackageName, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo)3771         private void notifyDeviceSuggestionsUpdatedOnHandler(
3772                 String routerPackageName,
3773                 String suggestingPackageName,
3774                 @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) {
3775             MediaRouter2ServiceImpl service = mServiceRef.get();
3776             if (service == null) {
3777                 return;
3778             }
3779             List<IMediaRouter2Manager> managers = new ArrayList<>();
3780             synchronized (service.mLock) {
3781                 for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) {
3782                     if (TextUtils.equals(managerRecord.mTargetPackageName, routerPackageName)) {
3783                         managers.add(managerRecord.mManager);
3784                     }
3785                 }
3786                 for (IMediaRouter2Manager manager : managers) {
3787                     try {
3788                         manager.notifyDeviceSuggestionsUpdated(
3789                                 routerPackageName, suggestingPackageName, suggestedDeviceInfo);
3790                     } catch (RemoteException ex) {
3791                         Slog.w(
3792                                 TAG,
3793                                 "Failed to notify suggesteion changed. Manager probably died.",
3794                                 ex);
3795                     }
3796                 }
3797                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
3798                     if (TextUtils.equals(routerRecord.mPackageName, routerPackageName)) {
3799                         routerRecord.notifyDeviceSuggestionsUpdated(
3800                                 suggestingPackageName, suggestedDeviceInfo);
3801                     }
3802                 }
3803             }
3804         }
3805 
updateDiscoveryPreferenceOnHandler()3806         private void updateDiscoveryPreferenceOnHandler() {
3807             MediaRouter2ServiceImpl service = mServiceRef.get();
3808             if (service == null) {
3809                 return;
3810             }
3811             List<RouterRecord> activeRouterRecords;
3812             List<RouterRecord> allRouterRecords = getRouterRecords();
3813 
3814             boolean areManagersScanning = areManagersScanning(service, getManagerRecords());
3815 
3816             if (areManagersScanning) {
3817                 activeRouterRecords = allRouterRecords;
3818             } else {
3819                 activeRouterRecords = getIndividuallyActiveRouters(service, allRouterRecords);
3820             }
3821 
3822             updateManagerScanningForProviders(areManagersScanning);
3823 
3824             Set<String> activelyScanningPackages = new HashSet<>();
3825             RouteDiscoveryPreference newPreference =
3826                     buildCompositeDiscoveryPreference(
3827                             activeRouterRecords, areManagersScanning, activelyScanningPackages);
3828 
3829             Slog.i(
3830                     TAG,
3831                     TextUtils.formatSimple(
3832                             "Updating composite discovery preference | preference: %s, active"
3833                                     + " routers: %s",
3834                             newPreference, activelyScanningPackages));
3835 
3836             if (updateScanningOnUserRecord(service, activelyScanningPackages, newPreference)) {
3837                 updateDiscoveryPreferenceForProviders(activelyScanningPackages);
3838             }
3839         }
3840 
updateDiscoveryPreferenceForProviders(Set<String> activelyScanningPackages)3841         private void updateDiscoveryPreferenceForProviders(Set<String> activelyScanningPackages) {
3842             for (MediaRoute2Provider provider : mRouteProviders) {
3843                 provider.updateDiscoveryPreference(
3844                         activelyScanningPackages, mUserRecord.mCompositeDiscoveryPreference);
3845             }
3846         }
3847 
updateScanningOnUserRecord( MediaRouter2ServiceImpl service, Set<String> activelyScanningPackages, RouteDiscoveryPreference newPreference)3848         private boolean updateScanningOnUserRecord(
3849                 MediaRouter2ServiceImpl service,
3850                 Set<String> activelyScanningPackages,
3851                 RouteDiscoveryPreference newPreference) {
3852             synchronized (service.mLock) {
3853                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)
3854                         && activelyScanningPackages.equals(mUserRecord.mActivelyScanningPackages)) {
3855                     return false;
3856                 }
3857 
3858                 var oldShouldPerformActiveScan =
3859                         mUserRecord.mCompositeDiscoveryPreference.shouldPerformActiveScan();
3860                 var newShouldPerformActiveScan = newPreference.shouldPerformActiveScan();
3861                 if (oldShouldPerformActiveScan != newShouldPerformActiveScan) {
3862                     // State access is synchronized with service.mLock.
3863                     // Linter still fails due to b/323906305#comment3
3864                     mMediaRouterMetricLogger.updateScanningState(newShouldPerformActiveScan);
3865                 }
3866 
3867                 mUserRecord.mCompositeDiscoveryPreference = newPreference;
3868                 mUserRecord.mActivelyScanningPackages = activelyScanningPackages;
3869             }
3870             return true;
3871         }
3872 
3873         /**
3874          * Returns a composite {@link RouteDiscoveryPreference} that aggregates every router
3875          * record's individual discovery preference.
3876          *
3877          * <p>The {@link RouteDiscoveryPreference#shouldPerformActiveScan() active scan value} of
3878          * the composite discovery preference is true if one of the router records is actively
3879          * scanning or if {@code shouldForceActiveScan} is true.
3880          *
3881          * <p>The composite RouteDiscoveryPreference is used to query route providers once to obtain
3882          * all the routes of interest, which can be subsequently filtered for the individual
3883          * discovery preferences.
3884          */
3885         @NonNull
buildCompositeDiscoveryPreference( List<RouterRecord> activeRouterRecords, boolean shouldForceActiveScan, Set<String> activelyScanningPackages)3886         private static RouteDiscoveryPreference buildCompositeDiscoveryPreference(
3887                 List<RouterRecord> activeRouterRecords,
3888                 boolean shouldForceActiveScan,
3889                 Set<String> activelyScanningPackages) {
3890             Set<String> preferredFeatures = new HashSet<>();
3891             boolean activeScan = false;
3892             for (RouterRecord activeRouterRecord : activeRouterRecords) {
3893                 RouteDiscoveryPreference preference = activeRouterRecord.mDiscoveryPreference;
3894                 preferredFeatures.addAll(preference.getPreferredFeatures());
3895 
3896                 boolean isRouterRecordActivelyScanning =
3897                         Flags.enablePreventionOfManagerScansWhenNoAppsScan()
3898                                 ? (activeRouterRecord.isActivelyScanning() || shouldForceActiveScan)
3899                                         && !preference.getPreferredFeatures().isEmpty()
3900                                 : activeRouterRecord.isActivelyScanning();
3901 
3902                 if (isRouterRecordActivelyScanning) {
3903                     activeScan = true;
3904                     activelyScanningPackages.add(activeRouterRecord.mPackageName);
3905                 }
3906             }
3907             return new RouteDiscoveryPreference.Builder(
3908                             List.copyOf(preferredFeatures), activeScan || shouldForceActiveScan)
3909                     .build();
3910         }
3911 
updateManagerScanningForProviders(boolean isManagerScanning)3912         private void updateManagerScanningForProviders(boolean isManagerScanning) {
3913             for (MediaRoute2Provider provider : mRouteProviders) {
3914                 if (provider instanceof MediaRoute2ProviderServiceProxy) {
3915                     ((MediaRoute2ProviderServiceProxy) provider)
3916                             .setManagerScanning(isManagerScanning);
3917                 }
3918             }
3919         }
3920 
3921         @NonNull
getIndividuallyActiveRouters( MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords)3922         private static List<RouterRecord> getIndividuallyActiveRouters(
3923                 MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
3924             if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
3925                 return Collections.emptyList();
3926             }
3927 
3928             return allRouterRecords.stream()
3929                     .filter(
3930                             record ->
3931                                     isPackageImportanceSufficientForScanning(
3932                                                     service, record.mPackageName)
3933                                             || record.mScanningState
3934                                                     == SCANNING_STATE_SCANNING_FULL)
3935                     .collect(Collectors.toList());
3936         }
3937 
areManagersScanning( MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords)3938         private static boolean areManagersScanning(
3939                 MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
3940             if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
3941                 return false;
3942             }
3943 
3944             return managerRecords.stream().anyMatch(manager ->
3945                     (manager.mScanningState == SCANNING_STATE_WHILE_INTERACTIVE
3946                             && isPackageImportanceSufficientForScanning(service,
3947                             manager.mOwnerPackageName))
3948                             || manager.mScanningState == SCANNING_STATE_SCANNING_FULL);
3949         }
3950 
isPackageImportanceSufficientForScanning( MediaRouter2ServiceImpl service, String packageName)3951         private static boolean isPackageImportanceSufficientForScanning(
3952                 MediaRouter2ServiceImpl service, String packageName) {
3953             return service.mActivityManager.getPackageImportance(packageName)
3954                     <= REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING;
3955         }
3956 
findProvider(@ullable String providerId)3957         private MediaRoute2Provider findProvider(@Nullable String providerId) {
3958             for (MediaRoute2Provider provider : mRouteProviders) {
3959                 if (TextUtils.equals(provider.getUniqueId(), providerId)) {
3960                     return provider;
3961                 }
3962             }
3963             return null;
3964         }
3965     }
3966 
3967     static final class SessionCreationRequest {
3968         public final RouterRecord mRouterRecord;
3969         public final long mUniqueRequestId;
3970         public final long mManagerRequestId;
3971         public final RoutingSessionInfo mOldSession;
3972         public final MediaRoute2Info mRoute;
3973 
SessionCreationRequest(@onNull RouterRecord routerRecord, long uniqueRequestId, long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route)3974         SessionCreationRequest(@NonNull RouterRecord routerRecord, long uniqueRequestId,
3975                 long managerRequestId, @NonNull RoutingSessionInfo oldSession,
3976                 @NonNull MediaRoute2Info route) {
3977             mRouterRecord = routerRecord;
3978             mUniqueRequestId = uniqueRequestId;
3979             mManagerRequestId = managerRequestId;
3980             mOldSession = oldSession;
3981             mRoute = route;
3982         }
3983 
dump(@onNull PrintWriter pw, @NonNull String prefix)3984         public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
3985             pw.println(prefix + "SessionCreationRequest");
3986 
3987             String indent = prefix + "  ";
3988 
3989             pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
3990             pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
3991             mOldSession.dump(pw, indent);
3992             mRoute.dump(pw, prefix);
3993         }
3994     }
3995 }
3996