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