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