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