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 enforceMediaContentControlPermission()441 public void enforceMediaContentControlPermission() { 442 mService2.enforceMediaContentControlPermission(); 443 } 444 445 // Binder call 446 @Override getSystemRoutes()447 public List<MediaRoute2Info> getSystemRoutes() { 448 return mService2.getSystemRoutes(); 449 } 450 451 // Binder call 452 @Override getSystemSessionInfo()453 public RoutingSessionInfo getSystemSessionInfo() { 454 return mService2.getSystemSessionInfo(); 455 } 456 457 // Binder call 458 @Override registerRouter2(IMediaRouter2 router, String packageName)459 public void registerRouter2(IMediaRouter2 router, String packageName) { 460 final int uid = Binder.getCallingUid(); 461 if (!validatePackageName(uid, packageName)) { 462 throw new SecurityException("packageName must match the calling uid"); 463 } 464 mService2.registerRouter2(router, packageName); 465 } 466 467 // Binder call 468 @Override unregisterRouter2(IMediaRouter2 router)469 public void unregisterRouter2(IMediaRouter2 router) { 470 mService2.unregisterRouter2(router); 471 } 472 473 // Binder call 474 @Override setDiscoveryRequestWithRouter2(IMediaRouter2 router, RouteDiscoveryPreference request)475 public void setDiscoveryRequestWithRouter2(IMediaRouter2 router, 476 RouteDiscoveryPreference request) { 477 mService2.setDiscoveryRequestWithRouter2(router, request); 478 } 479 480 // Binder call 481 @Override setRouteVolumeWithRouter2(IMediaRouter2 router, MediaRoute2Info route, int volume)482 public void setRouteVolumeWithRouter2(IMediaRouter2 router, 483 MediaRoute2Info route, int volume) { 484 mService2.setRouteVolumeWithRouter2(router, route, volume); 485 } 486 487 // Binder call 488 @Override requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route, Bundle sessionHints)489 public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, 490 long managerRequestId, RoutingSessionInfo oldSession, 491 MediaRoute2Info route, Bundle sessionHints) { 492 mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId, 493 oldSession, route, sessionHints); 494 } 495 496 // Binder call 497 @Override selectRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)498 public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, 499 MediaRoute2Info route) { 500 mService2.selectRouteWithRouter2(router, sessionId, route); 501 } 502 503 // Binder call 504 @Override deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)505 public void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, 506 MediaRoute2Info route) { 507 mService2.deselectRouteWithRouter2(router, sessionId, route); 508 } 509 510 // Binder call 511 @Override transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route)512 public void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, 513 MediaRoute2Info route) { 514 mService2.transferToRouteWithRouter2(router, sessionId, route); 515 } 516 517 // Binder call 518 @Override setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume)519 public void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume) { 520 mService2.setSessionVolumeWithRouter2(router, sessionId, volume); 521 } 522 523 // Binder call 524 @Override releaseSessionWithRouter2(IMediaRouter2 router, String sessionId)525 public void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId) { 526 mService2.releaseSessionWithRouter2(router, sessionId); 527 } 528 529 // Binder call 530 @Override getActiveSessions(IMediaRouter2Manager manager)531 public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) { 532 return mService2.getActiveSessions(manager); 533 } 534 535 // Binder call 536 @Override registerManager(IMediaRouter2Manager manager, String packageName)537 public void registerManager(IMediaRouter2Manager manager, String packageName) { 538 final int uid = Binder.getCallingUid(); 539 if (!validatePackageName(uid, packageName)) { 540 throw new SecurityException("packageName must match the calling uid"); 541 } 542 mService2.registerManager(manager, packageName); 543 } 544 545 // Binder call 546 @Override unregisterManager(IMediaRouter2Manager manager)547 public void unregisterManager(IMediaRouter2Manager manager) { 548 mService2.unregisterManager(manager); 549 } 550 551 // Binder call 552 @Override startScan(IMediaRouter2Manager manager)553 public void startScan(IMediaRouter2Manager manager) { 554 mService2.startScan(manager); 555 } 556 557 // Binder call 558 @Override stopScan(IMediaRouter2Manager manager)559 public void stopScan(IMediaRouter2Manager manager) { 560 mService2.stopScan(manager); 561 } 562 563 // Binder call 564 @Override setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, MediaRoute2Info route, int volume)565 public void setRouteVolumeWithManager(IMediaRouter2Manager manager, int requestId, 566 MediaRoute2Info route, int volume) { 567 mService2.setRouteVolumeWithManager(manager, requestId, route, volume); 568 } 569 570 // Binder call 571 @Override requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route)572 public void requestCreateSessionWithManager(IMediaRouter2Manager manager, 573 int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { 574 mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route); 575 } 576 577 // Binder call 578 @Override selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)579 public void selectRouteWithManager(IMediaRouter2Manager manager, int requestId, 580 String sessionId, MediaRoute2Info route) { 581 mService2.selectRouteWithManager(manager, requestId, sessionId, route); 582 } 583 584 // Binder call 585 @Override deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)586 public void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, 587 String sessionId, MediaRoute2Info route) { 588 mService2.deselectRouteWithManager(manager, requestId, sessionId, route); 589 } 590 591 // Binder call 592 @Override transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, MediaRoute2Info route)593 public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId, 594 String sessionId, MediaRoute2Info route) { 595 mService2.transferToRouteWithManager(manager, requestId, sessionId, route); 596 } 597 598 // Binder call 599 @Override setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, int volume)600 public void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, 601 String sessionId, int volume) { 602 mService2.setSessionVolumeWithManager(manager, requestId, sessionId, volume); 603 } 604 605 // Binder call 606 @Override releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId)607 public void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, 608 String sessionId) { 609 mService2.releaseSessionWithManager(manager, requestId, sessionId); 610 } 611 restoreBluetoothA2dp()612 void restoreBluetoothA2dp() { 613 try { 614 boolean a2dpOn; 615 BluetoothDevice btDevice; 616 synchronized (mLock) { 617 a2dpOn = mGlobalBluetoothA2dpOn; 618 btDevice = mActiveBluetoothDevice; 619 } 620 // We don't need to change a2dp status when bluetooth is not connected. 621 if (btDevice != null) { 622 if (DEBUG) { 623 Slog.d(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")"); 624 } 625 mAudioService.setBluetoothA2dpOn(a2dpOn); 626 } 627 } catch (RemoteException e) { 628 Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn."); 629 } 630 } 631 restoreRoute(int uid)632 void restoreRoute(int uid) { 633 ClientRecord clientRecord = null; 634 synchronized (mLock) { 635 UserRecord userRecord = mUserRecords.get( 636 UserHandle.getUserHandleForUid(uid).getIdentifier()); 637 if (userRecord != null && userRecord.mClientRecords != null) { 638 for (ClientRecord cr : userRecord.mClientRecords) { 639 if (validatePackageName(uid, cr.mPackageName)) { 640 clientRecord = cr; 641 break; 642 } 643 } 644 } 645 } 646 if (clientRecord != null) { 647 try { 648 clientRecord.mClient.onRestoreRoute(); 649 } catch (RemoteException e) { 650 Slog.w(TAG, "Failed to call onRestoreRoute. Client probably died."); 651 } 652 } else { 653 restoreBluetoothA2dp(); 654 } 655 } 656 switchUser()657 void switchUser() { 658 synchronized (mLock) { 659 int userId = ActivityManager.getCurrentUser(); 660 if (mCurrentUserId != userId) { 661 final int oldUserId = mCurrentUserId; 662 mCurrentUserId = userId; // do this first 663 664 UserRecord oldUser = mUserRecords.get(oldUserId); 665 if (oldUser != null) { 666 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP); 667 disposeUserIfNeededLocked(oldUser); // since no longer current user 668 } 669 670 UserRecord newUser = mUserRecords.get(userId); 671 if (newUser != null) { 672 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START); 673 } 674 } 675 } 676 mService2.switchUser(); 677 } 678 clientDied(ClientRecord clientRecord)679 void clientDied(ClientRecord clientRecord) { 680 synchronized (mLock) { 681 unregisterClientLocked(clientRecord.mClient, true); 682 } 683 } 684 registerClientLocked(IMediaRouterClient client, int uid, int pid, String packageName, int userId, boolean trusted)685 private void registerClientLocked(IMediaRouterClient client, 686 int uid, int pid, String packageName, int userId, boolean trusted) { 687 final IBinder binder = client.asBinder(); 688 ClientRecord clientRecord = mAllClientRecords.get(binder); 689 if (clientRecord == null) { 690 boolean newUser = false; 691 UserRecord userRecord = mUserRecords.get(userId); 692 if (userRecord == null) { 693 userRecord = new UserRecord(userId); 694 newUser = true; 695 } 696 clientRecord = new ClientRecord(userRecord, client, uid, pid, packageName, trusted); 697 try { 698 binder.linkToDeath(clientRecord, 0); 699 } catch (RemoteException ex) { 700 throw new RuntimeException("Media router client died prematurely.", ex); 701 } 702 703 if (newUser) { 704 mUserRecords.put(userId, userRecord); 705 initializeUserLocked(userRecord); 706 } 707 708 userRecord.mClientRecords.add(clientRecord); 709 mAllClientRecords.put(binder, clientRecord); 710 initializeClientLocked(clientRecord); 711 } 712 } 713 registerClientGroupIdLocked(IMediaRouterClient client, String groupId)714 private void registerClientGroupIdLocked(IMediaRouterClient client, String groupId) { 715 final IBinder binder = client.asBinder(); 716 ClientRecord clientRecord = mAllClientRecords.get(binder); 717 if (clientRecord == null) { 718 Log.w(TAG, "Ignoring group id register request of a unregistered client."); 719 return; 720 } 721 if (TextUtils.equals(clientRecord.mGroupId, groupId)) { 722 return; 723 } 724 UserRecord userRecord = clientRecord.mUserRecord; 725 if (clientRecord.mGroupId != null) { 726 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord); 727 } 728 clientRecord.mGroupId = groupId; 729 if (groupId != null) { 730 userRecord.addToGroup(groupId, clientRecord); 731 userRecord.mHandler.obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId) 732 .sendToTarget(); 733 } 734 } 735 unregisterClientLocked(IMediaRouterClient client, boolean died)736 private void unregisterClientLocked(IMediaRouterClient client, boolean died) { 737 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); 738 if (clientRecord != null) { 739 UserRecord userRecord = clientRecord.mUserRecord; 740 userRecord.mClientRecords.remove(clientRecord); 741 if (clientRecord.mGroupId != null) { 742 userRecord.removeFromGroup(clientRecord.mGroupId, clientRecord); 743 clientRecord.mGroupId = null; 744 } 745 disposeClientLocked(clientRecord, died); 746 disposeUserIfNeededLocked(userRecord); // since client removed from user 747 } 748 } 749 getStateLocked(IMediaRouterClient client)750 private MediaRouterClientState getStateLocked(IMediaRouterClient client) { 751 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); 752 if (clientRecord != null) { 753 return clientRecord.getState(); 754 } 755 return null; 756 } 757 setDiscoveryRequestLocked(IMediaRouterClient client, int routeTypes, boolean activeScan)758 private void setDiscoveryRequestLocked(IMediaRouterClient client, 759 int routeTypes, boolean activeScan) { 760 final IBinder binder = client.asBinder(); 761 ClientRecord clientRecord = mAllClientRecords.get(binder); 762 if (clientRecord != null) { 763 // Only let the system discover remote display routes for now. 764 if (!clientRecord.mTrusted) { 765 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; 766 } 767 768 if (clientRecord.mRouteTypes != routeTypes 769 || clientRecord.mActiveScan != activeScan) { 770 if (DEBUG) { 771 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x" 772 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan); 773 } 774 clientRecord.mRouteTypes = routeTypes; 775 clientRecord.mActiveScan = activeScan; 776 clientRecord.mUserRecord.mHandler.sendEmptyMessage( 777 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); 778 } 779 } 780 } 781 setSelectedRouteLocked(IMediaRouterClient client, String routeId, boolean explicit)782 private void setSelectedRouteLocked(IMediaRouterClient client, 783 String routeId, boolean explicit) { 784 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); 785 if (clientRecord != null) { 786 final String oldRouteId = clientRecord.mSelectedRouteId; 787 if (!Objects.equals(routeId, oldRouteId)) { 788 if (DEBUG) { 789 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId 790 + ", oldRouteId=" + oldRouteId 791 + ", explicit=" + explicit); 792 } 793 794 clientRecord.mSelectedRouteId = routeId; 795 // Only let the system connect to new global routes for now. 796 // A similar check exists in the display manager for wifi display. 797 if (explicit && clientRecord.mTrusted) { 798 if (oldRouteId != null) { 799 clientRecord.mUserRecord.mHandler.obtainMessage( 800 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget(); 801 } 802 if (routeId != null) { 803 clientRecord.mUserRecord.mHandler.obtainMessage( 804 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget(); 805 } 806 if (clientRecord.mGroupId != null) { 807 ClientGroup group = 808 clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId); 809 if (group != null) { 810 group.mSelectedRouteId = routeId; 811 clientRecord.mUserRecord.mHandler.obtainMessage( 812 UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, clientRecord.mGroupId) 813 .sendToTarget(); 814 } 815 } 816 } 817 } 818 } 819 } 820 requestSetVolumeLocked(IMediaRouterClient client, String routeId, int volume)821 private void requestSetVolumeLocked(IMediaRouterClient client, 822 String routeId, int volume) { 823 final IBinder binder = client.asBinder(); 824 ClientRecord clientRecord = mAllClientRecords.get(binder); 825 if (clientRecord != null) { 826 clientRecord.mUserRecord.mHandler.obtainMessage( 827 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget(); 828 } 829 } 830 requestUpdateVolumeLocked(IMediaRouterClient client, String routeId, int direction)831 private void requestUpdateVolumeLocked(IMediaRouterClient client, 832 String routeId, int direction) { 833 final IBinder binder = client.asBinder(); 834 ClientRecord clientRecord = mAllClientRecords.get(binder); 835 if (clientRecord != null) { 836 clientRecord.mUserRecord.mHandler.obtainMessage( 837 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget(); 838 } 839 } 840 initializeUserLocked(UserRecord userRecord)841 private void initializeUserLocked(UserRecord userRecord) { 842 if (DEBUG) { 843 Slog.d(TAG, userRecord + ": Initialized"); 844 } 845 if (userRecord.mUserId == mCurrentUserId) { 846 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START); 847 } 848 } 849 disposeUserIfNeededLocked(UserRecord userRecord)850 private void disposeUserIfNeededLocked(UserRecord userRecord) { 851 // If there are no records left and the user is no longer current then go ahead 852 // and purge the user record and all of its associated state. If the user is current 853 // then leave it alone since we might be connected to a route or want to query 854 // the same route information again soon. 855 if (userRecord.mUserId != mCurrentUserId 856 && userRecord.mClientRecords.isEmpty()) { 857 if (DEBUG) { 858 Slog.d(TAG, userRecord + ": Disposed"); 859 } 860 mUserRecords.remove(userRecord.mUserId); 861 // Note: User already stopped (by switchUser) so no need to send stop message here. 862 } 863 } 864 initializeClientLocked(ClientRecord clientRecord)865 private void initializeClientLocked(ClientRecord clientRecord) { 866 if (DEBUG) { 867 Slog.d(TAG, clientRecord + ": Registered"); 868 } 869 } 870 disposeClientLocked(ClientRecord clientRecord, boolean died)871 private void disposeClientLocked(ClientRecord clientRecord, boolean died) { 872 if (DEBUG) { 873 if (died) { 874 Slog.d(TAG, clientRecord + ": Died!"); 875 } else { 876 Slog.d(TAG, clientRecord + ": Unregistered"); 877 } 878 } 879 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) { 880 clientRecord.mUserRecord.mHandler.sendEmptyMessage( 881 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); 882 } 883 clientRecord.dispose(); 884 } 885 validatePackageName(int uid, String packageName)886 private boolean validatePackageName(int uid, String packageName) { 887 if (packageName != null) { 888 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); 889 if (packageNames != null) { 890 for (String n : packageNames) { 891 if (n.equals(packageName)) { 892 return true; 893 } 894 } 895 } 896 } 897 return false; 898 } 899 900 final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver { 901 @Override onReceive(Context context, Intent intent)902 public void onReceive(Context context, Intent intent) { 903 if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { 904 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 905 synchronized (mLock) { 906 boolean wasA2dpOn = mGlobalBluetoothA2dpOn; 907 mActiveBluetoothDevice = btDevice; 908 mGlobalBluetoothA2dpOn = btDevice != null; 909 if (wasA2dpOn != mGlobalBluetoothA2dpOn) { 910 Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '" 911 + mGlobalBluetoothA2dpOn + "'"); 912 UserRecord userRecord = mUserRecords.get(mCurrentUserId); 913 if (userRecord != null) { 914 for (ClientRecord cr : userRecord.mClientRecords) { 915 // mSelectedRouteId will be null for BT and phone speaker. 916 if (cr.mSelectedRouteId == null) { 917 try { 918 cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn); 919 } catch (RemoteException e) { 920 // Ignore exception 921 } 922 } 923 } 924 } 925 } 926 } 927 } 928 } 929 } 930 931 /** 932 * Information about a particular client of the media router. 933 * The contents of this object is guarded by mLock. 934 */ 935 final class ClientRecord implements DeathRecipient { 936 public final UserRecord mUserRecord; 937 public final IMediaRouterClient mClient; 938 public final int mUid; 939 public final int mPid; 940 public final String mPackageName; 941 public final boolean mTrusted; 942 public List<String> mControlCategories; 943 944 public int mRouteTypes; 945 public boolean mActiveScan; 946 public String mSelectedRouteId; 947 public String mGroupId; 948 ClientRecord(UserRecord userRecord, IMediaRouterClient client, int uid, int pid, String packageName, boolean trusted)949 public ClientRecord(UserRecord userRecord, IMediaRouterClient client, 950 int uid, int pid, String packageName, boolean trusted) { 951 mUserRecord = userRecord; 952 mClient = client; 953 mUid = uid; 954 mPid = pid; 955 mPackageName = packageName; 956 mTrusted = trusted; 957 } 958 dispose()959 public void dispose() { 960 mClient.asBinder().unlinkToDeath(this, 0); 961 } 962 963 @Override binderDied()964 public void binderDied() { 965 clientDied(this); 966 } 967 getState()968 MediaRouterClientState getState() { 969 return mTrusted ? mUserRecord.mRouterState : null; 970 } 971 dump(PrintWriter pw, String prefix)972 public void dump(PrintWriter pw, String prefix) { 973 pw.println(prefix + this); 974 975 final String indent = prefix + " "; 976 pw.println(indent + "mTrusted=" + mTrusted); 977 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes)); 978 pw.println(indent + "mActiveScan=" + mActiveScan); 979 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId); 980 } 981 982 @Override toString()983 public String toString() { 984 return "Client " + mPackageName + " (pid " + mPid + ")"; 985 } 986 } 987 988 final class ClientGroup { 989 public String mSelectedRouteId; 990 public final List<ClientRecord> mClientRecords = new ArrayList<>(); 991 } 992 993 /** 994 * Information about a particular user. 995 * The contents of this object is guarded by mLock. 996 */ 997 final class UserRecord { 998 public final int mUserId; 999 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<>(); 1000 public final UserHandler mHandler; 1001 public MediaRouterClientState mRouterState; 1002 private final ArrayMap<String, ClientGroup> mClientGroupMap = new ArrayMap<>(); 1003 UserRecord(int userId)1004 public UserRecord(int userId) { 1005 mUserId = userId; 1006 mHandler = new UserHandler(MediaRouterService.this, this); 1007 } 1008 dump(final PrintWriter pw, String prefix)1009 public void dump(final PrintWriter pw, String prefix) { 1010 pw.println(prefix + this); 1011 1012 final String indent = prefix + " "; 1013 final int clientCount = mClientRecords.size(); 1014 if (clientCount != 0) { 1015 for (int i = 0; i < clientCount; i++) { 1016 mClientRecords.get(i).dump(pw, indent); 1017 } 1018 } else { 1019 pw.println(indent + "<no clients>"); 1020 } 1021 1022 pw.println(indent + "State"); 1023 pw.println(indent + "mRouterState=" + mRouterState); 1024 1025 if (!mHandler.runWithScissors(new Runnable() { 1026 @Override 1027 public void run() { 1028 mHandler.dump(pw, indent); 1029 } 1030 }, 1000)) { 1031 pw.println(indent + "<could not dump handler state>"); 1032 } 1033 } 1034 addToGroup(String groupId, ClientRecord clientRecord)1035 public void addToGroup(String groupId, ClientRecord clientRecord) { 1036 ClientGroup group = mClientGroupMap.get(groupId); 1037 if (group == null) { 1038 group = new ClientGroup(); 1039 mClientGroupMap.put(groupId, group); 1040 } 1041 group.mClientRecords.add(clientRecord); 1042 } 1043 removeFromGroup(String groupId, ClientRecord clientRecord)1044 public void removeFromGroup(String groupId, ClientRecord clientRecord) { 1045 ClientGroup group = mClientGroupMap.get(groupId); 1046 if (group != null) { 1047 group.mClientRecords.remove(clientRecord); 1048 if (group.mClientRecords.size() == 0) { 1049 mClientGroupMap.remove(groupId); 1050 } 1051 } 1052 } 1053 1054 @Override toString()1055 public String toString() { 1056 return "User " + mUserId; 1057 } 1058 } 1059 1060 /** 1061 * Media router handler 1062 * <p> 1063 * Since remote display providers are designed to be single-threaded by nature, 1064 * this class encapsulates all of the associated functionality and exports state 1065 * to the service as it evolves. 1066 * </p><p> 1067 * This class is currently hardcoded to work with remote display providers but 1068 * it is intended to be eventually extended to support more general route providers 1069 * similar to the support library media router. 1070 * </p> 1071 */ 1072 static final class UserHandler extends Handler 1073 implements RemoteDisplayProviderWatcher.Callback, 1074 RemoteDisplayProviderProxy.Callback { 1075 public static final int MSG_START = 1; 1076 public static final int MSG_STOP = 2; 1077 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3; 1078 public static final int MSG_SELECT_ROUTE = 4; 1079 public static final int MSG_UNSELECT_ROUTE = 5; 1080 public static final int MSG_REQUEST_SET_VOLUME = 6; 1081 public static final int MSG_REQUEST_UPDATE_VOLUME = 7; 1082 private static final int MSG_UPDATE_CLIENT_STATE = 8; 1083 private static final int MSG_CONNECTION_TIMED_OUT = 9; 1084 private static final int MSG_NOTIFY_GROUP_ROUTE_SELECTED = 10; 1085 1086 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; 1087 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; 1088 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3; 1089 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4; 1090 1091 // The relative order of these constants is important and expresses progress 1092 // through the process of connecting to a route. 1093 private static final int PHASE_NOT_AVAILABLE = -1; 1094 private static final int PHASE_NOT_CONNECTED = 0; 1095 private static final int PHASE_CONNECTING = 1; 1096 private static final int PHASE_CONNECTED = 2; 1097 1098 private final MediaRouterService mService; 1099 private final UserRecord mUserRecord; 1100 private final RemoteDisplayProviderWatcher mWatcher; 1101 private final ArrayList<ProviderRecord> mProviderRecords = 1102 new ArrayList<ProviderRecord>(); 1103 private final ArrayList<IMediaRouterClient> mTempClients = 1104 new ArrayList<IMediaRouterClient>(); 1105 1106 private boolean mRunning; 1107 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; 1108 private RouteRecord mSelectedRouteRecord; 1109 private int mConnectionPhase = PHASE_NOT_AVAILABLE; 1110 private int mConnectionTimeoutReason; 1111 private long mConnectionTimeoutStartTime; 1112 private boolean mClientStateUpdateScheduled; 1113 UserHandler(MediaRouterService service, UserRecord userRecord)1114 public UserHandler(MediaRouterService service, UserRecord userRecord) { 1115 super(Looper.getMainLooper(), null, true); 1116 mService = service; 1117 mUserRecord = userRecord; 1118 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this, 1119 this, mUserRecord.mUserId); 1120 } 1121 1122 @Override handleMessage(Message msg)1123 public void handleMessage(Message msg) { 1124 switch (msg.what) { 1125 case MSG_START: { 1126 start(); 1127 break; 1128 } 1129 case MSG_STOP: { 1130 stop(); 1131 break; 1132 } 1133 case MSG_UPDATE_DISCOVERY_REQUEST: { 1134 updateDiscoveryRequest(); 1135 break; 1136 } 1137 case MSG_SELECT_ROUTE: { 1138 selectRoute((String)msg.obj); 1139 break; 1140 } 1141 case MSG_UNSELECT_ROUTE: { 1142 unselectRoute((String)msg.obj); 1143 break; 1144 } 1145 case MSG_REQUEST_SET_VOLUME: { 1146 requestSetVolume((String)msg.obj, msg.arg1); 1147 break; 1148 } 1149 case MSG_REQUEST_UPDATE_VOLUME: { 1150 requestUpdateVolume((String)msg.obj, msg.arg1); 1151 break; 1152 } 1153 case MSG_UPDATE_CLIENT_STATE: { 1154 updateClientState(); 1155 break; 1156 } 1157 case MSG_CONNECTION_TIMED_OUT: { 1158 connectionTimedOut(); 1159 break; 1160 } 1161 case MSG_NOTIFY_GROUP_ROUTE_SELECTED: { 1162 notifyGroupRouteSelected((String) msg.obj); 1163 break; 1164 } 1165 } 1166 } 1167 dump(PrintWriter pw, String prefix)1168 public void dump(PrintWriter pw, String prefix) { 1169 pw.println(prefix + "Handler"); 1170 1171 final String indent = prefix + " "; 1172 pw.println(indent + "mRunning=" + mRunning); 1173 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode); 1174 pw.println(indent + "mSelectedRouteRecord=" + mSelectedRouteRecord); 1175 pw.println(indent + "mConnectionPhase=" + mConnectionPhase); 1176 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason); 1177 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ? 1178 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>")); 1179 1180 mWatcher.dump(pw, prefix); 1181 1182 final int providerCount = mProviderRecords.size(); 1183 if (providerCount != 0) { 1184 for (int i = 0; i < providerCount; i++) { 1185 mProviderRecords.get(i).dump(pw, prefix); 1186 } 1187 } else { 1188 pw.println(indent + "<no providers>"); 1189 } 1190 } 1191 start()1192 private void start() { 1193 if (!mRunning) { 1194 mRunning = true; 1195 mWatcher.start(); // also starts all providers 1196 } 1197 } 1198 stop()1199 private void stop() { 1200 if (mRunning) { 1201 mRunning = false; 1202 unselectSelectedRoute(); 1203 mWatcher.stop(); // also stops all providers 1204 } 1205 } 1206 updateDiscoveryRequest()1207 private void updateDiscoveryRequest() { 1208 int routeTypes = 0; 1209 boolean activeScan = false; 1210 synchronized (mService.mLock) { 1211 final int count = mUserRecord.mClientRecords.size(); 1212 for (int i = 0; i < count; i++) { 1213 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i); 1214 routeTypes |= clientRecord.mRouteTypes; 1215 activeScan |= clientRecord.mActiveScan; 1216 } 1217 } 1218 1219 final int newDiscoveryMode; 1220 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) { 1221 if (activeScan) { 1222 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; 1223 } else { 1224 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; 1225 } 1226 } else { 1227 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; 1228 } 1229 1230 if (mDiscoveryMode != newDiscoveryMode) { 1231 mDiscoveryMode = newDiscoveryMode; 1232 final int count = mProviderRecords.size(); 1233 for (int i = 0; i < count; i++) { 1234 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode); 1235 } 1236 } 1237 } 1238 selectRoute(String routeId)1239 private void selectRoute(String routeId) { 1240 if (routeId != null 1241 && (mSelectedRouteRecord == null 1242 || !routeId.equals(mSelectedRouteRecord.getUniqueId()))) { 1243 RouteRecord routeRecord = findRouteRecord(routeId); 1244 if (routeRecord != null) { 1245 unselectSelectedRoute(); 1246 1247 Slog.i(TAG, "Selected route:" + routeRecord); 1248 mSelectedRouteRecord = routeRecord; 1249 checkSelectedRouteState(); 1250 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId()); 1251 1252 scheduleUpdateClientState(); 1253 } 1254 } 1255 } 1256 unselectRoute(String routeId)1257 private void unselectRoute(String routeId) { 1258 if (routeId != null 1259 && mSelectedRouteRecord != null 1260 && routeId.equals(mSelectedRouteRecord.getUniqueId())) { 1261 unselectSelectedRoute(); 1262 } 1263 } 1264 unselectSelectedRoute()1265 private void unselectSelectedRoute() { 1266 if (mSelectedRouteRecord != null) { 1267 Slog.i(TAG, "Unselected route:" + mSelectedRouteRecord); 1268 mSelectedRouteRecord.getProvider().setSelectedDisplay(null); 1269 mSelectedRouteRecord = null; 1270 checkSelectedRouteState(); 1271 1272 scheduleUpdateClientState(); 1273 } 1274 } 1275 requestSetVolume(String routeId, int volume)1276 private void requestSetVolume(String routeId, int volume) { 1277 if (mSelectedRouteRecord != null 1278 && routeId.equals(mSelectedRouteRecord.getUniqueId())) { 1279 mSelectedRouteRecord.getProvider().setDisplayVolume(volume); 1280 } 1281 } 1282 requestUpdateVolume(String routeId, int direction)1283 private void requestUpdateVolume(String routeId, int direction) { 1284 if (mSelectedRouteRecord != null 1285 && routeId.equals(mSelectedRouteRecord.getUniqueId())) { 1286 mSelectedRouteRecord.getProvider().adjustDisplayVolume(direction); 1287 } 1288 } 1289 1290 @Override addProvider(RemoteDisplayProviderProxy provider)1291 public void addProvider(RemoteDisplayProviderProxy provider) { 1292 provider.setCallback(this); 1293 provider.setDiscoveryMode(mDiscoveryMode); 1294 provider.setSelectedDisplay(null); // just to be safe 1295 1296 ProviderRecord providerRecord = new ProviderRecord(provider); 1297 mProviderRecords.add(providerRecord); 1298 providerRecord.updateDescriptor(provider.getDisplayState()); 1299 1300 scheduleUpdateClientState(); 1301 } 1302 1303 @Override removeProvider(RemoteDisplayProviderProxy provider)1304 public void removeProvider(RemoteDisplayProviderProxy provider) { 1305 int index = findProviderRecord(provider); 1306 if (index >= 0) { 1307 ProviderRecord providerRecord = mProviderRecords.remove(index); 1308 providerRecord.updateDescriptor(null); // mark routes invalid 1309 provider.setCallback(null); 1310 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE); 1311 1312 checkSelectedRouteState(); 1313 scheduleUpdateClientState(); 1314 } 1315 } 1316 1317 @Override onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state)1318 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider, 1319 RemoteDisplayState state) { 1320 updateProvider(provider, state); 1321 } 1322 updateProvider(RemoteDisplayProviderProxy provider, RemoteDisplayState state)1323 private void updateProvider(RemoteDisplayProviderProxy provider, 1324 RemoteDisplayState state) { 1325 int index = findProviderRecord(provider); 1326 if (index >= 0) { 1327 ProviderRecord providerRecord = mProviderRecords.get(index); 1328 if (providerRecord.updateDescriptor(state)) { 1329 checkSelectedRouteState(); 1330 scheduleUpdateClientState(); 1331 } 1332 } 1333 } 1334 1335 /** 1336 * This function is called whenever the state of the selected route may have changed. 1337 * It checks the state and updates timeouts or unselects the route as appropriate. 1338 */ checkSelectedRouteState()1339 private void checkSelectedRouteState() { 1340 // Unschedule timeouts when the route is unselected. 1341 if (mSelectedRouteRecord == null) { 1342 mConnectionPhase = PHASE_NOT_AVAILABLE; 1343 updateConnectionTimeout(0); 1344 return; 1345 } 1346 1347 // Ensure that the route is still present and enabled. 1348 if (!mSelectedRouteRecord.isValid() 1349 || !mSelectedRouteRecord.isEnabled()) { 1350 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); 1351 return; 1352 } 1353 1354 // Make sure we haven't lost our connection. 1355 final int oldPhase = mConnectionPhase; 1356 mConnectionPhase = getConnectionPhase(mSelectedRouteRecord.getStatus()); 1357 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) { 1358 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST); 1359 return; 1360 } 1361 1362 // Check the route status. 1363 switch (mConnectionPhase) { 1364 case PHASE_CONNECTED: 1365 if (oldPhase != PHASE_CONNECTED) { 1366 Slog.i(TAG, "Connected to route: " + mSelectedRouteRecord); 1367 } 1368 updateConnectionTimeout(0); 1369 break; 1370 case PHASE_CONNECTING: 1371 if (oldPhase != PHASE_CONNECTING) { 1372 Slog.i(TAG, "Connecting to route: " + mSelectedRouteRecord); 1373 } 1374 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED); 1375 break; 1376 case PHASE_NOT_CONNECTED: 1377 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING); 1378 break; 1379 case PHASE_NOT_AVAILABLE: 1380 default: 1381 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); 1382 break; 1383 } 1384 } 1385 updateConnectionTimeout(int reason)1386 private void updateConnectionTimeout(int reason) { 1387 if (reason != mConnectionTimeoutReason) { 1388 if (mConnectionTimeoutReason != 0) { 1389 removeMessages(MSG_CONNECTION_TIMED_OUT); 1390 } 1391 mConnectionTimeoutReason = reason; 1392 mConnectionTimeoutStartTime = SystemClock.uptimeMillis(); 1393 switch (reason) { 1394 case TIMEOUT_REASON_NOT_AVAILABLE: 1395 case TIMEOUT_REASON_CONNECTION_LOST: 1396 // Route became unavailable or connection lost. 1397 // Unselect it immediately. 1398 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT); 1399 break; 1400 case TIMEOUT_REASON_WAITING_FOR_CONNECTING: 1401 // Waiting for route to start connecting. 1402 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT); 1403 break; 1404 case TIMEOUT_REASON_WAITING_FOR_CONNECTED: 1405 // Waiting for route to complete connection. 1406 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT); 1407 break; 1408 } 1409 } 1410 } 1411 connectionTimedOut()1412 private void connectionTimedOut() { 1413 if (mConnectionTimeoutReason == 0 || mSelectedRouteRecord == null) { 1414 // Shouldn't get here. There must be a bug somewhere. 1415 Log.wtf(TAG, "Handled connection timeout for no reason."); 1416 return; 1417 } 1418 1419 switch (mConnectionTimeoutReason) { 1420 case TIMEOUT_REASON_NOT_AVAILABLE: 1421 Slog.i(TAG, "Selected route no longer available: " 1422 + mSelectedRouteRecord); 1423 break; 1424 case TIMEOUT_REASON_CONNECTION_LOST: 1425 Slog.i(TAG, "Selected route connection lost: " 1426 + mSelectedRouteRecord); 1427 break; 1428 case TIMEOUT_REASON_WAITING_FOR_CONNECTING: 1429 Slog.i(TAG, "Selected route timed out while waiting for " 1430 + "connection attempt to begin after " 1431 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) 1432 + " ms: " + mSelectedRouteRecord); 1433 break; 1434 case TIMEOUT_REASON_WAITING_FOR_CONNECTED: 1435 Slog.i(TAG, "Selected route timed out while connecting after " 1436 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) 1437 + " ms: " + mSelectedRouteRecord); 1438 break; 1439 } 1440 mConnectionTimeoutReason = 0; 1441 1442 unselectSelectedRoute(); 1443 } 1444 scheduleUpdateClientState()1445 private void scheduleUpdateClientState() { 1446 if (!mClientStateUpdateScheduled) { 1447 mClientStateUpdateScheduled = true; 1448 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE); 1449 } 1450 } 1451 updateClientState()1452 private void updateClientState() { 1453 mClientStateUpdateScheduled = false; 1454 1455 // Build a new client state for trusted clients. 1456 MediaRouterClientState routerState = new MediaRouterClientState(); 1457 final int providerCount = mProviderRecords.size(); 1458 for (int i = 0; i < providerCount; i++) { 1459 mProviderRecords.get(i).appendClientState(routerState); 1460 } 1461 try { 1462 synchronized (mService.mLock) { 1463 // Update the UserRecord. 1464 mUserRecord.mRouterState = routerState; 1465 1466 // Collect all clients. 1467 final int count = mUserRecord.mClientRecords.size(); 1468 for (int i = 0; i < count; i++) { 1469 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient); 1470 } 1471 } 1472 1473 // Notify all clients (outside of the lock). 1474 final int count = mTempClients.size(); 1475 for (int i = 0; i < count; i++) { 1476 try { 1477 mTempClients.get(i).onStateChanged(); 1478 } catch (RemoteException ex) { 1479 Slog.w(TAG, "Failed to call onStateChanged. Client probably died."); 1480 } 1481 } 1482 } finally { 1483 // Clear the list in preparation for the next time. 1484 mTempClients.clear(); 1485 } 1486 } 1487 notifyGroupRouteSelected(String groupId)1488 private void notifyGroupRouteSelected(String groupId) { 1489 try { 1490 String selectedRouteId; 1491 synchronized (mService.mLock) { 1492 ClientGroup group = mUserRecord.mClientGroupMap.get(groupId); 1493 if (group == null) { 1494 return; 1495 } 1496 selectedRouteId = group.mSelectedRouteId; 1497 final int count = group.mClientRecords.size(); 1498 for (int i = 0; i < count; i++) { 1499 ClientRecord clientRecord = group.mClientRecords.get(i); 1500 if (!TextUtils.equals(selectedRouteId, clientRecord.mSelectedRouteId)) { 1501 mTempClients.add(clientRecord.mClient); 1502 } 1503 } 1504 } 1505 1506 final int count = mTempClients.size(); 1507 for (int i = 0; i < count; i++) { 1508 try { 1509 mTempClients.get(i).onGroupRouteSelected(selectedRouteId); 1510 } catch (RemoteException ex) { 1511 Slog.w(TAG, "Failed to call onSelectedRouteChanged. Client probably died."); 1512 } 1513 } 1514 } finally { 1515 mTempClients.clear(); 1516 } 1517 } 1518 findProviderRecord(RemoteDisplayProviderProxy provider)1519 private int findProviderRecord(RemoteDisplayProviderProxy provider) { 1520 final int count = mProviderRecords.size(); 1521 for (int i = 0; i < count; i++) { 1522 ProviderRecord record = mProviderRecords.get(i); 1523 if (record.getProvider() == provider) { 1524 return i; 1525 } 1526 } 1527 return -1; 1528 } 1529 findRouteRecord(String uniqueId)1530 private RouteRecord findRouteRecord(String uniqueId) { 1531 final int count = mProviderRecords.size(); 1532 for (int i = 0; i < count; i++) { 1533 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId); 1534 if (record != null) { 1535 return record; 1536 } 1537 } 1538 return null; 1539 } 1540 getConnectionPhase(int status)1541 private static int getConnectionPhase(int status) { 1542 switch (status) { 1543 case MediaRouter.RouteInfo.STATUS_NONE: 1544 case MediaRouter.RouteInfo.STATUS_CONNECTED: 1545 return PHASE_CONNECTED; 1546 case MediaRouter.RouteInfo.STATUS_CONNECTING: 1547 return PHASE_CONNECTING; 1548 case MediaRouter.RouteInfo.STATUS_SCANNING: 1549 case MediaRouter.RouteInfo.STATUS_AVAILABLE: 1550 return PHASE_NOT_CONNECTED; 1551 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: 1552 case MediaRouter.RouteInfo.STATUS_IN_USE: 1553 default: 1554 return PHASE_NOT_AVAILABLE; 1555 } 1556 } 1557 1558 static final class ProviderRecord { 1559 private final RemoteDisplayProviderProxy mProvider; 1560 private final String mUniquePrefix; 1561 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>(); 1562 private RemoteDisplayState mDescriptor; 1563 ProviderRecord(RemoteDisplayProviderProxy provider)1564 public ProviderRecord(RemoteDisplayProviderProxy provider) { 1565 mProvider = provider; 1566 mUniquePrefix = provider.getFlattenedComponentName() + ":"; 1567 } 1568 getProvider()1569 public RemoteDisplayProviderProxy getProvider() { 1570 return mProvider; 1571 } 1572 getUniquePrefix()1573 public String getUniquePrefix() { 1574 return mUniquePrefix; 1575 } 1576 updateDescriptor(RemoteDisplayState descriptor)1577 public boolean updateDescriptor(RemoteDisplayState descriptor) { 1578 boolean changed = false; 1579 if (mDescriptor != descriptor) { 1580 mDescriptor = descriptor; 1581 1582 // Update all existing routes and reorder them to match 1583 // the order of their descriptors. 1584 int targetIndex = 0; 1585 if (descriptor != null) { 1586 if (descriptor.isValid()) { 1587 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays; 1588 final int routeCount = routeDescriptors.size(); 1589 for (int i = 0; i < routeCount; i++) { 1590 final RemoteDisplayInfo routeDescriptor = 1591 routeDescriptors.get(i); 1592 final String descriptorId = routeDescriptor.id; 1593 final int sourceIndex = findRouteByDescriptorId(descriptorId); 1594 if (sourceIndex < 0) { 1595 // Add the route to the provider. 1596 String uniqueId = assignRouteUniqueId(descriptorId); 1597 RouteRecord route = 1598 new RouteRecord(this, descriptorId, uniqueId); 1599 mRoutes.add(targetIndex++, route); 1600 route.updateDescriptor(routeDescriptor); 1601 changed = true; 1602 } else if (sourceIndex < targetIndex) { 1603 // Ignore route with duplicate id. 1604 Slog.w(TAG, "Ignoring route descriptor with duplicate id: " 1605 + routeDescriptor); 1606 } else { 1607 // Reorder existing route within the list. 1608 RouteRecord route = mRoutes.get(sourceIndex); 1609 Collections.swap(mRoutes, sourceIndex, targetIndex++); 1610 changed |= route.updateDescriptor(routeDescriptor); 1611 } 1612 } 1613 } else { 1614 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: " 1615 + mProvider.getFlattenedComponentName()); 1616 } 1617 } 1618 1619 // Dispose all remaining routes that do not have matching descriptors. 1620 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) { 1621 RouteRecord route = mRoutes.remove(i); 1622 route.updateDescriptor(null); // mark route invalid 1623 changed = true; 1624 } 1625 } 1626 return changed; 1627 } 1628 appendClientState(MediaRouterClientState state)1629 public void appendClientState(MediaRouterClientState state) { 1630 final int routeCount = mRoutes.size(); 1631 for (int i = 0; i < routeCount; i++) { 1632 state.routes.add(mRoutes.get(i).getInfo()); 1633 } 1634 } 1635 findRouteByUniqueId(String uniqueId)1636 public RouteRecord findRouteByUniqueId(String uniqueId) { 1637 final int routeCount = mRoutes.size(); 1638 for (int i = 0; i < routeCount; i++) { 1639 RouteRecord route = mRoutes.get(i); 1640 if (route.getUniqueId().equals(uniqueId)) { 1641 return route; 1642 } 1643 } 1644 return null; 1645 } 1646 findRouteByDescriptorId(String descriptorId)1647 private int findRouteByDescriptorId(String descriptorId) { 1648 final int routeCount = mRoutes.size(); 1649 for (int i = 0; i < routeCount; i++) { 1650 RouteRecord route = mRoutes.get(i); 1651 if (route.getDescriptorId().equals(descriptorId)) { 1652 return i; 1653 } 1654 } 1655 return -1; 1656 } 1657 dump(PrintWriter pw, String prefix)1658 public void dump(PrintWriter pw, String prefix) { 1659 pw.println(prefix + this); 1660 1661 final String indent = prefix + " "; 1662 mProvider.dump(pw, indent); 1663 1664 final int routeCount = mRoutes.size(); 1665 if (routeCount != 0) { 1666 for (int i = 0; i < routeCount; i++) { 1667 mRoutes.get(i).dump(pw, indent); 1668 } 1669 } else { 1670 pw.println(indent + "<no routes>"); 1671 } 1672 } 1673 1674 @Override toString()1675 public String toString() { 1676 return "Provider " + mProvider.getFlattenedComponentName(); 1677 } 1678 assignRouteUniqueId(String descriptorId)1679 private String assignRouteUniqueId(String descriptorId) { 1680 return mUniquePrefix + descriptorId; 1681 } 1682 } 1683 1684 static final class RouteRecord { 1685 private final ProviderRecord mProviderRecord; 1686 private final String mDescriptorId; 1687 private final MediaRouterClientState.RouteInfo mMutableInfo; 1688 private MediaRouterClientState.RouteInfo mImmutableInfo; 1689 private RemoteDisplayInfo mDescriptor; 1690 RouteRecord(ProviderRecord providerRecord, String descriptorId, String uniqueId)1691 public RouteRecord(ProviderRecord providerRecord, 1692 String descriptorId, String uniqueId) { 1693 mProviderRecord = providerRecord; 1694 mDescriptorId = descriptorId; 1695 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId); 1696 } 1697 getProvider()1698 public RemoteDisplayProviderProxy getProvider() { 1699 return mProviderRecord.getProvider(); 1700 } 1701 getProviderRecord()1702 public ProviderRecord getProviderRecord() { 1703 return mProviderRecord; 1704 } 1705 getDescriptorId()1706 public String getDescriptorId() { 1707 return mDescriptorId; 1708 } 1709 getUniqueId()1710 public String getUniqueId() { 1711 return mMutableInfo.id; 1712 } 1713 getInfo()1714 public MediaRouterClientState.RouteInfo getInfo() { 1715 if (mImmutableInfo == null) { 1716 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo); 1717 } 1718 return mImmutableInfo; 1719 } 1720 isValid()1721 public boolean isValid() { 1722 return mDescriptor != null; 1723 } 1724 isEnabled()1725 public boolean isEnabled() { 1726 return mMutableInfo.enabled; 1727 } 1728 getStatus()1729 public int getStatus() { 1730 return mMutableInfo.statusCode; 1731 } 1732 updateDescriptor(RemoteDisplayInfo descriptor)1733 public boolean updateDescriptor(RemoteDisplayInfo descriptor) { 1734 boolean changed = false; 1735 if (mDescriptor != descriptor) { 1736 mDescriptor = descriptor; 1737 if (descriptor != null) { 1738 final String name = computeName(descriptor); 1739 if (!Objects.equals(mMutableInfo.name, name)) { 1740 mMutableInfo.name = name; 1741 changed = true; 1742 } 1743 final String description = computeDescription(descriptor); 1744 if (!Objects.equals(mMutableInfo.description, description)) { 1745 mMutableInfo.description = description; 1746 changed = true; 1747 } 1748 final int supportedTypes = computeSupportedTypes(descriptor); 1749 if (mMutableInfo.supportedTypes != supportedTypes) { 1750 mMutableInfo.supportedTypes = supportedTypes; 1751 changed = true; 1752 } 1753 final boolean enabled = computeEnabled(descriptor); 1754 if (mMutableInfo.enabled != enabled) { 1755 mMutableInfo.enabled = enabled; 1756 changed = true; 1757 } 1758 final int statusCode = computeStatusCode(descriptor); 1759 if (mMutableInfo.statusCode != statusCode) { 1760 mMutableInfo.statusCode = statusCode; 1761 changed = true; 1762 } 1763 final int playbackType = computePlaybackType(descriptor); 1764 if (mMutableInfo.playbackType != playbackType) { 1765 mMutableInfo.playbackType = playbackType; 1766 changed = true; 1767 } 1768 final int playbackStream = computePlaybackStream(descriptor); 1769 if (mMutableInfo.playbackStream != playbackStream) { 1770 mMutableInfo.playbackStream = playbackStream; 1771 changed = true; 1772 } 1773 final int volume = computeVolume(descriptor); 1774 if (mMutableInfo.volume != volume) { 1775 mMutableInfo.volume = volume; 1776 changed = true; 1777 } 1778 final int volumeMax = computeVolumeMax(descriptor); 1779 if (mMutableInfo.volumeMax != volumeMax) { 1780 mMutableInfo.volumeMax = volumeMax; 1781 changed = true; 1782 } 1783 final int volumeHandling = computeVolumeHandling(descriptor); 1784 if (mMutableInfo.volumeHandling != volumeHandling) { 1785 mMutableInfo.volumeHandling = volumeHandling; 1786 changed = true; 1787 } 1788 final int presentationDisplayId = computePresentationDisplayId(descriptor); 1789 if (mMutableInfo.presentationDisplayId != presentationDisplayId) { 1790 mMutableInfo.presentationDisplayId = presentationDisplayId; 1791 changed = true; 1792 } 1793 } 1794 } 1795 if (changed) { 1796 mImmutableInfo = null; 1797 } 1798 return changed; 1799 } 1800 dump(PrintWriter pw, String prefix)1801 public void dump(PrintWriter pw, String prefix) { 1802 pw.println(prefix + this); 1803 1804 final String indent = prefix + " "; 1805 pw.println(indent + "mMutableInfo=" + mMutableInfo); 1806 pw.println(indent + "mDescriptorId=" + mDescriptorId); 1807 pw.println(indent + "mDescriptor=" + mDescriptor); 1808 } 1809 1810 @Override toString()1811 public String toString() { 1812 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")"; 1813 } 1814 computeName(RemoteDisplayInfo descriptor)1815 private static String computeName(RemoteDisplayInfo descriptor) { 1816 // Note that isValid() already ensures the name is non-empty. 1817 return descriptor.name; 1818 } 1819 computeDescription(RemoteDisplayInfo descriptor)1820 private static String computeDescription(RemoteDisplayInfo descriptor) { 1821 final String description = descriptor.description; 1822 return TextUtils.isEmpty(description) ? null : description; 1823 } 1824 computeSupportedTypes(RemoteDisplayInfo descriptor)1825 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) { 1826 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO 1827 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO 1828 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; 1829 } 1830 computeEnabled(RemoteDisplayInfo descriptor)1831 private static boolean computeEnabled(RemoteDisplayInfo descriptor) { 1832 switch (descriptor.status) { 1833 case RemoteDisplayInfo.STATUS_CONNECTED: 1834 case RemoteDisplayInfo.STATUS_CONNECTING: 1835 case RemoteDisplayInfo.STATUS_AVAILABLE: 1836 return true; 1837 default: 1838 return false; 1839 } 1840 } 1841 computeStatusCode(RemoteDisplayInfo descriptor)1842 private static int computeStatusCode(RemoteDisplayInfo descriptor) { 1843 switch (descriptor.status) { 1844 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE: 1845 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE; 1846 case RemoteDisplayInfo.STATUS_AVAILABLE: 1847 return MediaRouter.RouteInfo.STATUS_AVAILABLE; 1848 case RemoteDisplayInfo.STATUS_IN_USE: 1849 return MediaRouter.RouteInfo.STATUS_IN_USE; 1850 case RemoteDisplayInfo.STATUS_CONNECTING: 1851 return MediaRouter.RouteInfo.STATUS_CONNECTING; 1852 case RemoteDisplayInfo.STATUS_CONNECTED: 1853 return MediaRouter.RouteInfo.STATUS_CONNECTED; 1854 default: 1855 return MediaRouter.RouteInfo.STATUS_NONE; 1856 } 1857 } 1858 computePlaybackType(RemoteDisplayInfo descriptor)1859 private static int computePlaybackType(RemoteDisplayInfo descriptor) { 1860 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; 1861 } 1862 computePlaybackStream(RemoteDisplayInfo descriptor)1863 private static int computePlaybackStream(RemoteDisplayInfo descriptor) { 1864 return AudioSystem.STREAM_MUSIC; 1865 } 1866 computeVolume(RemoteDisplayInfo descriptor)1867 private static int computeVolume(RemoteDisplayInfo descriptor) { 1868 final int volume = descriptor.volume; 1869 final int volumeMax = descriptor.volumeMax; 1870 if (volume < 0) { 1871 return 0; 1872 } else if (volume > volumeMax) { 1873 return volumeMax; 1874 } 1875 return volume; 1876 } 1877 computeVolumeMax(RemoteDisplayInfo descriptor)1878 private static int computeVolumeMax(RemoteDisplayInfo descriptor) { 1879 final int volumeMax = descriptor.volumeMax; 1880 return volumeMax > 0 ? volumeMax : 0; 1881 } 1882 computeVolumeHandling(RemoteDisplayInfo descriptor)1883 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) { 1884 final int volumeHandling = descriptor.volumeHandling; 1885 switch (volumeHandling) { 1886 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE: 1887 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; 1888 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED: 1889 default: 1890 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; 1891 } 1892 } 1893 computePresentationDisplayId(RemoteDisplayInfo descriptor)1894 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) { 1895 // The MediaRouter class validates that the id corresponds to an extant 1896 // presentation display. So all we do here is canonicalize the null case. 1897 final int displayId = descriptor.presentationDisplayId; 1898 return displayId < 0 ? -1 : displayId; 1899 } 1900 } 1901 } 1902 } 1903