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