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 com.android.internal.util.DumpUtils; 20 import com.android.server.Watchdog; 21 22 import android.Manifest; 23 import android.app.ActivityManager; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.media.AudioSystem; 30 import android.media.IMediaRouterClient; 31 import android.media.IMediaRouterService; 32 import android.media.MediaRouter; 33 import android.media.MediaRouterClientState; 34 import android.media.RemoteDisplayState; 35 import android.media.RemoteDisplayState.RemoteDisplayInfo; 36 import android.os.Binder; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.os.SystemClock; 43 import android.text.TextUtils; 44 import android.util.ArrayMap; 45 import android.util.Log; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 import android.util.TimeUtils; 49 50 import java.io.FileDescriptor; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.Objects; 56 57 /** 58 * Provides a mechanism for discovering media routes and manages media playback 59 * behalf of applications. 60 * <p> 61 * Currently supports discovering remote displays via remote display provider 62 * services that have been registered by applications. 63 * </p> 64 */ 65 public final class MediaRouterService extends IMediaRouterService.Stub 66 implements Watchdog.Monitor { 67 private static final String TAG = "MediaRouterService"; 68 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 69 70 /** 71 * Timeout in milliseconds for a selected route to transition from a 72 * disconnected state to a connecting state. If we don't observe any 73 * progress within this interval, then we will give up and unselect the route. 74 */ 75 static final long CONNECTING_TIMEOUT = 5000; 76 77 /** 78 * Timeout in milliseconds for a selected route to transition from a 79 * connecting state to a connected state. If we don't observe any 80 * progress within this interval, then we will give up and unselect the route. 81 */ 82 static final long CONNECTED_TIMEOUT = 60000; 83 84 private final Context mContext; 85 86 // State guarded by mLock. 87 private final Object mLock = new Object(); 88 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); 89 private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = 90 new ArrayMap<IBinder, ClientRecord>(); 91 private int mCurrentUserId = -1; 92 93 public MediaRouterService(Context context) { 94 mContext = context; 95 Watchdog.getInstance().addMonitor(this); 96 } 97 98 public void systemRunning() { 99 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); 100 mContext.registerReceiver(new BroadcastReceiver() { 101 @Override 102 public void onReceive(Context context, Intent intent) { 103 if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) { 104 switchUser(); 105 } 106 } 107 }, filter); 108 109 switchUser(); 110 } 111 112 @Override 113 public void monitor() { 114 synchronized (mLock) { /* check for deadlock */ } 115 } 116 117 // Binder call 118 @Override 119 public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) { 120 if (client == null) { 121 throw new IllegalArgumentException("client must not be null"); 122 } 123 124 final int uid = Binder.getCallingUid(); 125 if (!validatePackageName(uid, packageName)) { 126 throw new SecurityException("packageName must match the calling uid"); 127 } 128 129 final int pid = Binder.getCallingPid(); 130 final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, 131 false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName); 132 final boolean trusted = mContext.checkCallingOrSelfPermission( 133 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == 134 PackageManager.PERMISSION_GRANTED; 135 final long token = Binder.clearCallingIdentity(); 136 try { 137 synchronized (mLock) { 138 registerClientLocked(client, pid, packageName, resolvedUserId, trusted); 139 } 140 } finally { 141 Binder.restoreCallingIdentity(token); 142 } 143 } 144 145 // Binder call 146 @Override 147 public void unregisterClient(IMediaRouterClient client) { 148 if (client == null) { 149 throw new IllegalArgumentException("client must not be null"); 150 } 151 152 final long token = Binder.clearCallingIdentity(); 153 try { 154 synchronized (mLock) { 155 unregisterClientLocked(client, false); 156 } 157 } finally { 158 Binder.restoreCallingIdentity(token); 159 } 160 } 161 162 // Binder call 163 @Override 164 public MediaRouterClientState getState(IMediaRouterClient client) { 165 if (client == null) { 166 throw new IllegalArgumentException("client must not be null"); 167 } 168 169 final long token = Binder.clearCallingIdentity(); 170 try { 171 synchronized (mLock) { 172 return getStateLocked(client); 173 } 174 } finally { 175 Binder.restoreCallingIdentity(token); 176 } 177 } 178 179 // Binder call 180 @Override 181 public void setDiscoveryRequest(IMediaRouterClient client, 182 int routeTypes, boolean activeScan) { 183 if (client == null) { 184 throw new IllegalArgumentException("client must not be null"); 185 } 186 187 final long token = Binder.clearCallingIdentity(); 188 try { 189 synchronized (mLock) { 190 setDiscoveryRequestLocked(client, routeTypes, activeScan); 191 } 192 } finally { 193 Binder.restoreCallingIdentity(token); 194 } 195 } 196 197 // Binder call 198 // A null routeId means that the client wants to unselect its current route. 199 // The explicit flag indicates whether the change was explicitly requested by the 200 // user or the application which may cause changes to propagate out to the rest 201 // of the system. Should be false when the change is in response to a new globally 202 // selected route or a default selection. 203 @Override 204 public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) { 205 if (client == null) { 206 throw new IllegalArgumentException("client must not be null"); 207 } 208 209 final long token = Binder.clearCallingIdentity(); 210 try { 211 synchronized (mLock) { 212 setSelectedRouteLocked(client, routeId, explicit); 213 } 214 } finally { 215 Binder.restoreCallingIdentity(token); 216 } 217 } 218 219 // Binder call 220 @Override 221 public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) { 222 if (client == null) { 223 throw new IllegalArgumentException("client must not be null"); 224 } 225 if (routeId == null) { 226 throw new IllegalArgumentException("routeId must not be null"); 227 } 228 229 final long token = Binder.clearCallingIdentity(); 230 try { 231 synchronized (mLock) { 232 requestSetVolumeLocked(client, routeId, volume); 233 } 234 } finally { 235 Binder.restoreCallingIdentity(token); 236 } 237 } 238 239 // Binder call 240 @Override 241 public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) { 242 if (client == null) { 243 throw new IllegalArgumentException("client must not be null"); 244 } 245 if (routeId == null) { 246 throw new IllegalArgumentException("routeId must not be null"); 247 } 248 249 final long token = Binder.clearCallingIdentity(); 250 try { 251 synchronized (mLock) { 252 requestUpdateVolumeLocked(client, routeId, direction); 253 } 254 } finally { 255 Binder.restoreCallingIdentity(token); 256 } 257 } 258 259 // Binder call 260 @Override 261 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 262 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 263 264 pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)"); 265 pw.println(); 266 pw.println("Global state"); 267 pw.println(" mCurrentUserId=" + mCurrentUserId); 268 269 synchronized (mLock) { 270 final int count = mUserRecords.size(); 271 for (int i = 0; i < count; i++) { 272 UserRecord userRecord = mUserRecords.valueAt(i); 273 pw.println(); 274 userRecord.dump(pw, ""); 275 } 276 } 277 } 278 279 void switchUser() { 280 synchronized (mLock) { 281 int userId = ActivityManager.getCurrentUser(); 282 if (mCurrentUserId != userId) { 283 final int oldUserId = mCurrentUserId; 284 mCurrentUserId = userId; // do this first 285 286 UserRecord oldUser = mUserRecords.get(oldUserId); 287 if (oldUser != null) { 288 oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP); 289 disposeUserIfNeededLocked(oldUser); // since no longer current user 290 } 291 292 UserRecord newUser = mUserRecords.get(userId); 293 if (newUser != null) { 294 newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START); 295 } 296 } 297 } 298 } 299 300 void clientDied(ClientRecord clientRecord) { 301 synchronized (mLock) { 302 unregisterClientLocked(clientRecord.mClient, true); 303 } 304 } 305 306 private void registerClientLocked(IMediaRouterClient client, 307 int pid, String packageName, int userId, boolean trusted) { 308 final IBinder binder = client.asBinder(); 309 ClientRecord clientRecord = mAllClientRecords.get(binder); 310 if (clientRecord == null) { 311 boolean newUser = false; 312 UserRecord userRecord = mUserRecords.get(userId); 313 if (userRecord == null) { 314 userRecord = new UserRecord(userId); 315 newUser = true; 316 } 317 clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted); 318 try { 319 binder.linkToDeath(clientRecord, 0); 320 } catch (RemoteException ex) { 321 throw new RuntimeException("Media router client died prematurely.", ex); 322 } 323 324 if (newUser) { 325 mUserRecords.put(userId, userRecord); 326 initializeUserLocked(userRecord); 327 } 328 329 userRecord.mClientRecords.add(clientRecord); 330 mAllClientRecords.put(binder, clientRecord); 331 initializeClientLocked(clientRecord); 332 } 333 } 334 335 private void unregisterClientLocked(IMediaRouterClient client, boolean died) { 336 ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); 337 if (clientRecord != null) { 338 UserRecord userRecord = clientRecord.mUserRecord; 339 userRecord.mClientRecords.remove(clientRecord); 340 disposeClientLocked(clientRecord, died); 341 disposeUserIfNeededLocked(userRecord); // since client removed from user 342 } 343 } 344 345 private MediaRouterClientState getStateLocked(IMediaRouterClient client) { 346 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); 347 if (clientRecord != null) { 348 return clientRecord.getState(); 349 } 350 return null; 351 } 352 353 private void setDiscoveryRequestLocked(IMediaRouterClient client, 354 int routeTypes, boolean activeScan) { 355 final IBinder binder = client.asBinder(); 356 ClientRecord clientRecord = mAllClientRecords.get(binder); 357 if (clientRecord != null) { 358 // Only let the system discover remote display routes for now. 359 if (!clientRecord.mTrusted) { 360 routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; 361 } 362 363 if (clientRecord.mRouteTypes != routeTypes 364 || clientRecord.mActiveScan != activeScan) { 365 if (DEBUG) { 366 Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x" 367 + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan); 368 } 369 clientRecord.mRouteTypes = routeTypes; 370 clientRecord.mActiveScan = activeScan; 371 clientRecord.mUserRecord.mHandler.sendEmptyMessage( 372 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); 373 } 374 } 375 } 376 377 private void setSelectedRouteLocked(IMediaRouterClient client, 378 String routeId, boolean explicit) { 379 ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); 380 if (clientRecord != null) { 381 final String oldRouteId = clientRecord.mSelectedRouteId; 382 if (!Objects.equals(routeId, oldRouteId)) { 383 if (DEBUG) { 384 Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId 385 + ", oldRouteId=" + oldRouteId 386 + ", explicit=" + explicit); 387 } 388 389 clientRecord.mSelectedRouteId = routeId; 390 if (explicit) { 391 // Any app can disconnect from the globally selected route. 392 if (oldRouteId != null) { 393 clientRecord.mUserRecord.mHandler.obtainMessage( 394 UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget(); 395 } 396 // Only let the system connect to new global routes for now. 397 // A similar check exists in the display manager for wifi display. 398 if (routeId != null && clientRecord.mTrusted) { 399 clientRecord.mUserRecord.mHandler.obtainMessage( 400 UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget(); 401 } 402 } 403 } 404 } 405 } 406 407 private void requestSetVolumeLocked(IMediaRouterClient client, 408 String routeId, int volume) { 409 final IBinder binder = client.asBinder(); 410 ClientRecord clientRecord = mAllClientRecords.get(binder); 411 if (clientRecord != null) { 412 clientRecord.mUserRecord.mHandler.obtainMessage( 413 UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget(); 414 } 415 } 416 417 private void requestUpdateVolumeLocked(IMediaRouterClient client, 418 String routeId, int direction) { 419 final IBinder binder = client.asBinder(); 420 ClientRecord clientRecord = mAllClientRecords.get(binder); 421 if (clientRecord != null) { 422 clientRecord.mUserRecord.mHandler.obtainMessage( 423 UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget(); 424 } 425 } 426 427 private void initializeUserLocked(UserRecord userRecord) { 428 if (DEBUG) { 429 Slog.d(TAG, userRecord + ": Initialized"); 430 } 431 if (userRecord.mUserId == mCurrentUserId) { 432 userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START); 433 } 434 } 435 436 private void disposeUserIfNeededLocked(UserRecord userRecord) { 437 // If there are no records left and the user is no longer current then go ahead 438 // and purge the user record and all of its associated state. If the user is current 439 // then leave it alone since we might be connected to a route or want to query 440 // the same route information again soon. 441 if (userRecord.mUserId != mCurrentUserId 442 && userRecord.mClientRecords.isEmpty()) { 443 if (DEBUG) { 444 Slog.d(TAG, userRecord + ": Disposed"); 445 } 446 mUserRecords.remove(userRecord.mUserId); 447 // Note: User already stopped (by switchUser) so no need to send stop message here. 448 } 449 } 450 451 private void initializeClientLocked(ClientRecord clientRecord) { 452 if (DEBUG) { 453 Slog.d(TAG, clientRecord + ": Registered"); 454 } 455 } 456 457 private void disposeClientLocked(ClientRecord clientRecord, boolean died) { 458 if (DEBUG) { 459 if (died) { 460 Slog.d(TAG, clientRecord + ": Died!"); 461 } else { 462 Slog.d(TAG, clientRecord + ": Unregistered"); 463 } 464 } 465 if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) { 466 clientRecord.mUserRecord.mHandler.sendEmptyMessage( 467 UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); 468 } 469 clientRecord.dispose(); 470 } 471 472 private boolean validatePackageName(int uid, String packageName) { 473 if (packageName != null) { 474 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); 475 if (packageNames != null) { 476 for (String n : packageNames) { 477 if (n.equals(packageName)) { 478 return true; 479 } 480 } 481 } 482 } 483 return false; 484 } 485 486 /** 487 * Information about a particular client of the media router. 488 * The contents of this object is guarded by mLock. 489 */ 490 final class ClientRecord implements DeathRecipient { 491 public final UserRecord mUserRecord; 492 public final IMediaRouterClient mClient; 493 public final int mPid; 494 public final String mPackageName; 495 public final boolean mTrusted; 496 497 public int mRouteTypes; 498 public boolean mActiveScan; 499 public String mSelectedRouteId; 500 501 public ClientRecord(UserRecord userRecord, IMediaRouterClient client, 502 int pid, String packageName, boolean trusted) { 503 mUserRecord = userRecord; 504 mClient = client; 505 mPid = pid; 506 mPackageName = packageName; 507 mTrusted = trusted; 508 } 509 510 public void dispose() { 511 mClient.asBinder().unlinkToDeath(this, 0); 512 } 513 514 @Override 515 public void binderDied() { 516 clientDied(this); 517 } 518 519 MediaRouterClientState getState() { 520 return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState; 521 } 522 523 public void dump(PrintWriter pw, String prefix) { 524 pw.println(prefix + this); 525 526 final String indent = prefix + " "; 527 pw.println(indent + "mTrusted=" + mTrusted); 528 pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes)); 529 pw.println(indent + "mActiveScan=" + mActiveScan); 530 pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId); 531 } 532 533 @Override 534 public String toString() { 535 return "Client " + mPackageName + " (pid " + mPid + ")"; 536 } 537 } 538 539 /** 540 * Information about a particular user. 541 * The contents of this object is guarded by mLock. 542 */ 543 final class UserRecord { 544 public final int mUserId; 545 public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>(); 546 public final UserHandler mHandler; 547 public MediaRouterClientState mTrustedState; 548 public MediaRouterClientState mUntrustedState; 549 550 public UserRecord(int userId) { 551 mUserId = userId; 552 mHandler = new UserHandler(MediaRouterService.this, this); 553 } 554 555 public void dump(final PrintWriter pw, String prefix) { 556 pw.println(prefix + this); 557 558 final String indent = prefix + " "; 559 final int clientCount = mClientRecords.size(); 560 if (clientCount != 0) { 561 for (int i = 0; i < clientCount; i++) { 562 mClientRecords.get(i).dump(pw, indent); 563 } 564 } else { 565 pw.println(indent + "<no clients>"); 566 } 567 568 pw.println(indent + "State"); 569 pw.println(indent + "mTrustedState=" + mTrustedState); 570 pw.println(indent + "mUntrustedState=" + mUntrustedState); 571 572 if (!mHandler.runWithScissors(new Runnable() { 573 @Override 574 public void run() { 575 mHandler.dump(pw, indent); 576 } 577 }, 1000)) { 578 pw.println(indent + "<could not dump handler state>"); 579 } 580 } 581 582 @Override 583 public String toString() { 584 return "User " + mUserId; 585 } 586 } 587 588 /** 589 * Media router handler 590 * <p> 591 * Since remote display providers are designed to be single-threaded by nature, 592 * this class encapsulates all of the associated functionality and exports state 593 * to the service as it evolves. 594 * </p><p> 595 * One important task of this class is to keep track of the current globally selected 596 * route id for certain routes that have global effects, such as remote displays. 597 * Global route selections override local selections made within apps. The change 598 * is propagated to all apps so that they are all in sync. Synchronization works 599 * both ways. Whenever the globally selected route is explicitly unselected by any 600 * app, then it becomes unselected globally and all apps are informed. 601 * </p><p> 602 * This class is currently hardcoded to work with remote display providers but 603 * it is intended to be eventually extended to support more general route providers 604 * similar to the support library media router. 605 * </p> 606 */ 607 static final class UserHandler extends Handler 608 implements RemoteDisplayProviderWatcher.Callback, 609 RemoteDisplayProviderProxy.Callback { 610 public static final int MSG_START = 1; 611 public static final int MSG_STOP = 2; 612 public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3; 613 public static final int MSG_SELECT_ROUTE = 4; 614 public static final int MSG_UNSELECT_ROUTE = 5; 615 public static final int MSG_REQUEST_SET_VOLUME = 6; 616 public static final int MSG_REQUEST_UPDATE_VOLUME = 7; 617 private static final int MSG_UPDATE_CLIENT_STATE = 8; 618 private static final int MSG_CONNECTION_TIMED_OUT = 9; 619 620 private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; 621 private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; 622 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3; 623 private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4; 624 625 // The relative order of these constants is important and expresses progress 626 // through the process of connecting to a route. 627 private static final int PHASE_NOT_AVAILABLE = -1; 628 private static final int PHASE_NOT_CONNECTED = 0; 629 private static final int PHASE_CONNECTING = 1; 630 private static final int PHASE_CONNECTED = 2; 631 632 private final MediaRouterService mService; 633 private final UserRecord mUserRecord; 634 private final RemoteDisplayProviderWatcher mWatcher; 635 private final ArrayList<ProviderRecord> mProviderRecords = 636 new ArrayList<ProviderRecord>(); 637 private final ArrayList<IMediaRouterClient> mTempClients = 638 new ArrayList<IMediaRouterClient>(); 639 640 private boolean mRunning; 641 private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; 642 private RouteRecord mGloballySelectedRouteRecord; 643 private int mConnectionPhase = PHASE_NOT_AVAILABLE; 644 private int mConnectionTimeoutReason; 645 private long mConnectionTimeoutStartTime; 646 private boolean mClientStateUpdateScheduled; 647 648 public UserHandler(MediaRouterService service, UserRecord userRecord) { 649 super(Looper.getMainLooper(), null, true); 650 mService = service; 651 mUserRecord = userRecord; 652 mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this, 653 this, mUserRecord.mUserId); 654 } 655 656 @Override 657 public void handleMessage(Message msg) { 658 switch (msg.what) { 659 case MSG_START: { 660 start(); 661 break; 662 } 663 case MSG_STOP: { 664 stop(); 665 break; 666 } 667 case MSG_UPDATE_DISCOVERY_REQUEST: { 668 updateDiscoveryRequest(); 669 break; 670 } 671 case MSG_SELECT_ROUTE: { 672 selectRoute((String)msg.obj); 673 break; 674 } 675 case MSG_UNSELECT_ROUTE: { 676 unselectRoute((String)msg.obj); 677 break; 678 } 679 case MSG_REQUEST_SET_VOLUME: { 680 requestSetVolume((String)msg.obj, msg.arg1); 681 break; 682 } 683 case MSG_REQUEST_UPDATE_VOLUME: { 684 requestUpdateVolume((String)msg.obj, msg.arg1); 685 break; 686 } 687 case MSG_UPDATE_CLIENT_STATE: { 688 updateClientState(); 689 break; 690 } 691 case MSG_CONNECTION_TIMED_OUT: { 692 connectionTimedOut(); 693 break; 694 } 695 } 696 } 697 698 public void dump(PrintWriter pw, String prefix) { 699 pw.println(prefix + "Handler"); 700 701 final String indent = prefix + " "; 702 pw.println(indent + "mRunning=" + mRunning); 703 pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode); 704 pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord); 705 pw.println(indent + "mConnectionPhase=" + mConnectionPhase); 706 pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason); 707 pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ? 708 TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>")); 709 710 mWatcher.dump(pw, prefix); 711 712 final int providerCount = mProviderRecords.size(); 713 if (providerCount != 0) { 714 for (int i = 0; i < providerCount; i++) { 715 mProviderRecords.get(i).dump(pw, prefix); 716 } 717 } else { 718 pw.println(indent + "<no providers>"); 719 } 720 } 721 722 private void start() { 723 if (!mRunning) { 724 mRunning = true; 725 mWatcher.start(); // also starts all providers 726 } 727 } 728 729 private void stop() { 730 if (mRunning) { 731 mRunning = false; 732 unselectGloballySelectedRoute(); 733 mWatcher.stop(); // also stops all providers 734 } 735 } 736 737 private void updateDiscoveryRequest() { 738 int routeTypes = 0; 739 boolean activeScan = false; 740 synchronized (mService.mLock) { 741 final int count = mUserRecord.mClientRecords.size(); 742 for (int i = 0; i < count; i++) { 743 ClientRecord clientRecord = mUserRecord.mClientRecords.get(i); 744 routeTypes |= clientRecord.mRouteTypes; 745 activeScan |= clientRecord.mActiveScan; 746 } 747 } 748 749 final int newDiscoveryMode; 750 if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) { 751 if (activeScan) { 752 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; 753 } else { 754 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; 755 } 756 } else { 757 newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; 758 } 759 760 if (mDiscoveryMode != newDiscoveryMode) { 761 mDiscoveryMode = newDiscoveryMode; 762 final int count = mProviderRecords.size(); 763 for (int i = 0; i < count; i++) { 764 mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode); 765 } 766 } 767 } 768 769 private void selectRoute(String routeId) { 770 if (routeId != null 771 && (mGloballySelectedRouteRecord == null 772 || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) { 773 RouteRecord routeRecord = findRouteRecord(routeId); 774 if (routeRecord != null) { 775 unselectGloballySelectedRoute(); 776 777 Slog.i(TAG, "Selected global route:" + routeRecord); 778 mGloballySelectedRouteRecord = routeRecord; 779 checkGloballySelectedRouteState(); 780 routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId()); 781 782 scheduleUpdateClientState(); 783 } 784 } 785 } 786 787 private void unselectRoute(String routeId) { 788 if (routeId != null 789 && mGloballySelectedRouteRecord != null 790 && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { 791 unselectGloballySelectedRoute(); 792 } 793 } 794 795 private void unselectGloballySelectedRoute() { 796 if (mGloballySelectedRouteRecord != null) { 797 Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord); 798 mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null); 799 mGloballySelectedRouteRecord = null; 800 checkGloballySelectedRouteState(); 801 802 scheduleUpdateClientState(); 803 } 804 } 805 806 private void requestSetVolume(String routeId, int volume) { 807 if (mGloballySelectedRouteRecord != null 808 && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { 809 mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume); 810 } 811 } 812 813 private void requestUpdateVolume(String routeId, int direction) { 814 if (mGloballySelectedRouteRecord != null 815 && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { 816 mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction); 817 } 818 } 819 820 @Override 821 public void addProvider(RemoteDisplayProviderProxy provider) { 822 provider.setCallback(this); 823 provider.setDiscoveryMode(mDiscoveryMode); 824 provider.setSelectedDisplay(null); // just to be safe 825 826 ProviderRecord providerRecord = new ProviderRecord(provider); 827 mProviderRecords.add(providerRecord); 828 providerRecord.updateDescriptor(provider.getDisplayState()); 829 830 scheduleUpdateClientState(); 831 } 832 833 @Override 834 public void removeProvider(RemoteDisplayProviderProxy provider) { 835 int index = findProviderRecord(provider); 836 if (index >= 0) { 837 ProviderRecord providerRecord = mProviderRecords.remove(index); 838 providerRecord.updateDescriptor(null); // mark routes invalid 839 provider.setCallback(null); 840 provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE); 841 842 checkGloballySelectedRouteState(); 843 scheduleUpdateClientState(); 844 } 845 } 846 847 @Override 848 public void onDisplayStateChanged(RemoteDisplayProviderProxy provider, 849 RemoteDisplayState state) { 850 updateProvider(provider, state); 851 } 852 853 private void updateProvider(RemoteDisplayProviderProxy provider, 854 RemoteDisplayState state) { 855 int index = findProviderRecord(provider); 856 if (index >= 0) { 857 ProviderRecord providerRecord = mProviderRecords.get(index); 858 if (providerRecord.updateDescriptor(state)) { 859 checkGloballySelectedRouteState(); 860 scheduleUpdateClientState(); 861 } 862 } 863 } 864 865 /** 866 * This function is called whenever the state of the globally selected route 867 * may have changed. It checks the state and updates timeouts or unselects 868 * the route as appropriate. 869 */ 870 private void checkGloballySelectedRouteState() { 871 // Unschedule timeouts when the route is unselected. 872 if (mGloballySelectedRouteRecord == null) { 873 mConnectionPhase = PHASE_NOT_AVAILABLE; 874 updateConnectionTimeout(0); 875 return; 876 } 877 878 // Ensure that the route is still present and enabled. 879 if (!mGloballySelectedRouteRecord.isValid() 880 || !mGloballySelectedRouteRecord.isEnabled()) { 881 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); 882 return; 883 } 884 885 // Make sure we haven't lost our connection. 886 final int oldPhase = mConnectionPhase; 887 mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus()); 888 if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) { 889 updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST); 890 return; 891 } 892 893 // Check the route status. 894 switch (mConnectionPhase) { 895 case PHASE_CONNECTED: 896 if (oldPhase != PHASE_CONNECTED) { 897 Slog.i(TAG, "Connected to global route: " 898 + mGloballySelectedRouteRecord); 899 } 900 updateConnectionTimeout(0); 901 break; 902 case PHASE_CONNECTING: 903 if (oldPhase != PHASE_CONNECTING) { 904 Slog.i(TAG, "Connecting to global route: " 905 + mGloballySelectedRouteRecord); 906 } 907 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED); 908 break; 909 case PHASE_NOT_CONNECTED: 910 updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING); 911 break; 912 case PHASE_NOT_AVAILABLE: 913 default: 914 updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); 915 break; 916 } 917 } 918 919 private void updateConnectionTimeout(int reason) { 920 if (reason != mConnectionTimeoutReason) { 921 if (mConnectionTimeoutReason != 0) { 922 removeMessages(MSG_CONNECTION_TIMED_OUT); 923 } 924 mConnectionTimeoutReason = reason; 925 mConnectionTimeoutStartTime = SystemClock.uptimeMillis(); 926 switch (reason) { 927 case TIMEOUT_REASON_NOT_AVAILABLE: 928 case TIMEOUT_REASON_CONNECTION_LOST: 929 // Route became unavailable or connection lost. 930 // Unselect it immediately. 931 sendEmptyMessage(MSG_CONNECTION_TIMED_OUT); 932 break; 933 case TIMEOUT_REASON_WAITING_FOR_CONNECTING: 934 // Waiting for route to start connecting. 935 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT); 936 break; 937 case TIMEOUT_REASON_WAITING_FOR_CONNECTED: 938 // Waiting for route to complete connection. 939 sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT); 940 break; 941 } 942 } 943 } 944 945 private void connectionTimedOut() { 946 if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) { 947 // Shouldn't get here. There must be a bug somewhere. 948 Log.wtf(TAG, "Handled connection timeout for no reason."); 949 return; 950 } 951 952 switch (mConnectionTimeoutReason) { 953 case TIMEOUT_REASON_NOT_AVAILABLE: 954 Slog.i(TAG, "Global route no longer available: " 955 + mGloballySelectedRouteRecord); 956 break; 957 case TIMEOUT_REASON_CONNECTION_LOST: 958 Slog.i(TAG, "Global route connection lost: " 959 + mGloballySelectedRouteRecord); 960 break; 961 case TIMEOUT_REASON_WAITING_FOR_CONNECTING: 962 Slog.i(TAG, "Global route timed out while waiting for " 963 + "connection attempt to begin after " 964 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) 965 + " ms: " + mGloballySelectedRouteRecord); 966 break; 967 case TIMEOUT_REASON_WAITING_FOR_CONNECTED: 968 Slog.i(TAG, "Global route timed out while connecting after " 969 + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) 970 + " ms: " + mGloballySelectedRouteRecord); 971 break; 972 } 973 mConnectionTimeoutReason = 0; 974 975 unselectGloballySelectedRoute(); 976 } 977 978 private void scheduleUpdateClientState() { 979 if (!mClientStateUpdateScheduled) { 980 mClientStateUpdateScheduled = true; 981 sendEmptyMessage(MSG_UPDATE_CLIENT_STATE); 982 } 983 } 984 985 private void updateClientState() { 986 mClientStateUpdateScheduled = false; 987 988 final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ? 989 mGloballySelectedRouteRecord.getUniqueId() : null; 990 991 // Build a new client state for trusted clients. 992 MediaRouterClientState trustedState = new MediaRouterClientState(); 993 trustedState.globallySelectedRouteId = globallySelectedRouteId; 994 final int providerCount = mProviderRecords.size(); 995 for (int i = 0; i < providerCount; i++) { 996 mProviderRecords.get(i).appendClientState(trustedState); 997 } 998 999 // Build a new client state for untrusted clients that can only see 1000 // the currently selected route. 1001 MediaRouterClientState untrustedState = new MediaRouterClientState(); 1002 untrustedState.globallySelectedRouteId = globallySelectedRouteId; 1003 if (globallySelectedRouteId != null) { 1004 untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId)); 1005 } 1006 1007 try { 1008 synchronized (mService.mLock) { 1009 // Update the UserRecord. 1010 mUserRecord.mTrustedState = trustedState; 1011 mUserRecord.mUntrustedState = untrustedState; 1012 1013 // Collect all clients. 1014 final int count = mUserRecord.mClientRecords.size(); 1015 for (int i = 0; i < count; i++) { 1016 mTempClients.add(mUserRecord.mClientRecords.get(i).mClient); 1017 } 1018 } 1019 1020 // Notify all clients (outside of the lock). 1021 final int count = mTempClients.size(); 1022 for (int i = 0; i < count; i++) { 1023 try { 1024 mTempClients.get(i).onStateChanged(); 1025 } catch (RemoteException ex) { 1026 // ignore errors, client probably died 1027 } 1028 } 1029 } finally { 1030 // Clear the list in preparation for the next time. 1031 mTempClients.clear(); 1032 } 1033 } 1034 1035 private int findProviderRecord(RemoteDisplayProviderProxy provider) { 1036 final int count = mProviderRecords.size(); 1037 for (int i = 0; i < count; i++) { 1038 ProviderRecord record = mProviderRecords.get(i); 1039 if (record.getProvider() == provider) { 1040 return i; 1041 } 1042 } 1043 return -1; 1044 } 1045 1046 private RouteRecord findRouteRecord(String uniqueId) { 1047 final int count = mProviderRecords.size(); 1048 for (int i = 0; i < count; i++) { 1049 RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId); 1050 if (record != null) { 1051 return record; 1052 } 1053 } 1054 return null; 1055 } 1056 1057 private static int getConnectionPhase(int status) { 1058 switch (status) { 1059 case MediaRouter.RouteInfo.STATUS_NONE: 1060 case MediaRouter.RouteInfo.STATUS_CONNECTED: 1061 return PHASE_CONNECTED; 1062 case MediaRouter.RouteInfo.STATUS_CONNECTING: 1063 return PHASE_CONNECTING; 1064 case MediaRouter.RouteInfo.STATUS_SCANNING: 1065 case MediaRouter.RouteInfo.STATUS_AVAILABLE: 1066 return PHASE_NOT_CONNECTED; 1067 case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: 1068 case MediaRouter.RouteInfo.STATUS_IN_USE: 1069 default: 1070 return PHASE_NOT_AVAILABLE; 1071 } 1072 } 1073 1074 static final class ProviderRecord { 1075 private final RemoteDisplayProviderProxy mProvider; 1076 private final String mUniquePrefix; 1077 private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>(); 1078 private RemoteDisplayState mDescriptor; 1079 1080 public ProviderRecord(RemoteDisplayProviderProxy provider) { 1081 mProvider = provider; 1082 mUniquePrefix = provider.getFlattenedComponentName() + ":"; 1083 } 1084 1085 public RemoteDisplayProviderProxy getProvider() { 1086 return mProvider; 1087 } 1088 1089 public String getUniquePrefix() { 1090 return mUniquePrefix; 1091 } 1092 1093 public boolean updateDescriptor(RemoteDisplayState descriptor) { 1094 boolean changed = false; 1095 if (mDescriptor != descriptor) { 1096 mDescriptor = descriptor; 1097 1098 // Update all existing routes and reorder them to match 1099 // the order of their descriptors. 1100 int targetIndex = 0; 1101 if (descriptor != null) { 1102 if (descriptor.isValid()) { 1103 final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays; 1104 final int routeCount = routeDescriptors.size(); 1105 for (int i = 0; i < routeCount; i++) { 1106 final RemoteDisplayInfo routeDescriptor = 1107 routeDescriptors.get(i); 1108 final String descriptorId = routeDescriptor.id; 1109 final int sourceIndex = findRouteByDescriptorId(descriptorId); 1110 if (sourceIndex < 0) { 1111 // Add the route to the provider. 1112 String uniqueId = assignRouteUniqueId(descriptorId); 1113 RouteRecord route = 1114 new RouteRecord(this, descriptorId, uniqueId); 1115 mRoutes.add(targetIndex++, route); 1116 route.updateDescriptor(routeDescriptor); 1117 changed = true; 1118 } else if (sourceIndex < targetIndex) { 1119 // Ignore route with duplicate id. 1120 Slog.w(TAG, "Ignoring route descriptor with duplicate id: " 1121 + routeDescriptor); 1122 } else { 1123 // Reorder existing route within the list. 1124 RouteRecord route = mRoutes.get(sourceIndex); 1125 Collections.swap(mRoutes, sourceIndex, targetIndex++); 1126 changed |= route.updateDescriptor(routeDescriptor); 1127 } 1128 } 1129 } else { 1130 Slog.w(TAG, "Ignoring invalid descriptor from media route provider: " 1131 + mProvider.getFlattenedComponentName()); 1132 } 1133 } 1134 1135 // Dispose all remaining routes that do not have matching descriptors. 1136 for (int i = mRoutes.size() - 1; i >= targetIndex; i--) { 1137 RouteRecord route = mRoutes.remove(i); 1138 route.updateDescriptor(null); // mark route invalid 1139 changed = true; 1140 } 1141 } 1142 return changed; 1143 } 1144 1145 public void appendClientState(MediaRouterClientState state) { 1146 final int routeCount = mRoutes.size(); 1147 for (int i = 0; i < routeCount; i++) { 1148 state.routes.add(mRoutes.get(i).getInfo()); 1149 } 1150 } 1151 1152 public RouteRecord findRouteByUniqueId(String uniqueId) { 1153 final int routeCount = mRoutes.size(); 1154 for (int i = 0; i < routeCount; i++) { 1155 RouteRecord route = mRoutes.get(i); 1156 if (route.getUniqueId().equals(uniqueId)) { 1157 return route; 1158 } 1159 } 1160 return null; 1161 } 1162 1163 private int findRouteByDescriptorId(String descriptorId) { 1164 final int routeCount = mRoutes.size(); 1165 for (int i = 0; i < routeCount; i++) { 1166 RouteRecord route = mRoutes.get(i); 1167 if (route.getDescriptorId().equals(descriptorId)) { 1168 return i; 1169 } 1170 } 1171 return -1; 1172 } 1173 1174 public void dump(PrintWriter pw, String prefix) { 1175 pw.println(prefix + this); 1176 1177 final String indent = prefix + " "; 1178 mProvider.dump(pw, indent); 1179 1180 final int routeCount = mRoutes.size(); 1181 if (routeCount != 0) { 1182 for (int i = 0; i < routeCount; i++) { 1183 mRoutes.get(i).dump(pw, indent); 1184 } 1185 } else { 1186 pw.println(indent + "<no routes>"); 1187 } 1188 } 1189 1190 @Override 1191 public String toString() { 1192 return "Provider " + mProvider.getFlattenedComponentName(); 1193 } 1194 1195 private String assignRouteUniqueId(String descriptorId) { 1196 return mUniquePrefix + descriptorId; 1197 } 1198 } 1199 1200 static final class RouteRecord { 1201 private final ProviderRecord mProviderRecord; 1202 private final String mDescriptorId; 1203 private final MediaRouterClientState.RouteInfo mMutableInfo; 1204 private MediaRouterClientState.RouteInfo mImmutableInfo; 1205 private RemoteDisplayInfo mDescriptor; 1206 1207 public RouteRecord(ProviderRecord providerRecord, 1208 String descriptorId, String uniqueId) { 1209 mProviderRecord = providerRecord; 1210 mDescriptorId = descriptorId; 1211 mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId); 1212 } 1213 1214 public RemoteDisplayProviderProxy getProvider() { 1215 return mProviderRecord.getProvider(); 1216 } 1217 1218 public ProviderRecord getProviderRecord() { 1219 return mProviderRecord; 1220 } 1221 1222 public String getDescriptorId() { 1223 return mDescriptorId; 1224 } 1225 1226 public String getUniqueId() { 1227 return mMutableInfo.id; 1228 } 1229 1230 public MediaRouterClientState.RouteInfo getInfo() { 1231 if (mImmutableInfo == null) { 1232 mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo); 1233 } 1234 return mImmutableInfo; 1235 } 1236 1237 public boolean isValid() { 1238 return mDescriptor != null; 1239 } 1240 1241 public boolean isEnabled() { 1242 return mMutableInfo.enabled; 1243 } 1244 1245 public int getStatus() { 1246 return mMutableInfo.statusCode; 1247 } 1248 1249 public boolean updateDescriptor(RemoteDisplayInfo descriptor) { 1250 boolean changed = false; 1251 if (mDescriptor != descriptor) { 1252 mDescriptor = descriptor; 1253 if (descriptor != null) { 1254 final String name = computeName(descriptor); 1255 if (!Objects.equals(mMutableInfo.name, name)) { 1256 mMutableInfo.name = name; 1257 changed = true; 1258 } 1259 final String description = computeDescription(descriptor); 1260 if (!Objects.equals(mMutableInfo.description, description)) { 1261 mMutableInfo.description = description; 1262 changed = true; 1263 } 1264 final int supportedTypes = computeSupportedTypes(descriptor); 1265 if (mMutableInfo.supportedTypes != supportedTypes) { 1266 mMutableInfo.supportedTypes = supportedTypes; 1267 changed = true; 1268 } 1269 final boolean enabled = computeEnabled(descriptor); 1270 if (mMutableInfo.enabled != enabled) { 1271 mMutableInfo.enabled = enabled; 1272 changed = true; 1273 } 1274 final int statusCode = computeStatusCode(descriptor); 1275 if (mMutableInfo.statusCode != statusCode) { 1276 mMutableInfo.statusCode = statusCode; 1277 changed = true; 1278 } 1279 final int playbackType = computePlaybackType(descriptor); 1280 if (mMutableInfo.playbackType != playbackType) { 1281 mMutableInfo.playbackType = playbackType; 1282 changed = true; 1283 } 1284 final int playbackStream = computePlaybackStream(descriptor); 1285 if (mMutableInfo.playbackStream != playbackStream) { 1286 mMutableInfo.playbackStream = playbackStream; 1287 changed = true; 1288 } 1289 final int volume = computeVolume(descriptor); 1290 if (mMutableInfo.volume != volume) { 1291 mMutableInfo.volume = volume; 1292 changed = true; 1293 } 1294 final int volumeMax = computeVolumeMax(descriptor); 1295 if (mMutableInfo.volumeMax != volumeMax) { 1296 mMutableInfo.volumeMax = volumeMax; 1297 changed = true; 1298 } 1299 final int volumeHandling = computeVolumeHandling(descriptor); 1300 if (mMutableInfo.volumeHandling != volumeHandling) { 1301 mMutableInfo.volumeHandling = volumeHandling; 1302 changed = true; 1303 } 1304 final int presentationDisplayId = computePresentationDisplayId(descriptor); 1305 if (mMutableInfo.presentationDisplayId != presentationDisplayId) { 1306 mMutableInfo.presentationDisplayId = presentationDisplayId; 1307 changed = true; 1308 } 1309 } 1310 } 1311 if (changed) { 1312 mImmutableInfo = null; 1313 } 1314 return changed; 1315 } 1316 1317 public void dump(PrintWriter pw, String prefix) { 1318 pw.println(prefix + this); 1319 1320 final String indent = prefix + " "; 1321 pw.println(indent + "mMutableInfo=" + mMutableInfo); 1322 pw.println(indent + "mDescriptorId=" + mDescriptorId); 1323 pw.println(indent + "mDescriptor=" + mDescriptor); 1324 } 1325 1326 @Override 1327 public String toString() { 1328 return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")"; 1329 } 1330 1331 private static String computeName(RemoteDisplayInfo descriptor) { 1332 // Note that isValid() already ensures the name is non-empty. 1333 return descriptor.name; 1334 } 1335 1336 private static String computeDescription(RemoteDisplayInfo descriptor) { 1337 final String description = descriptor.description; 1338 return TextUtils.isEmpty(description) ? null : description; 1339 } 1340 1341 private static int computeSupportedTypes(RemoteDisplayInfo descriptor) { 1342 return MediaRouter.ROUTE_TYPE_LIVE_AUDIO 1343 | MediaRouter.ROUTE_TYPE_LIVE_VIDEO 1344 | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; 1345 } 1346 1347 private static boolean computeEnabled(RemoteDisplayInfo descriptor) { 1348 switch (descriptor.status) { 1349 case RemoteDisplayInfo.STATUS_CONNECTED: 1350 case RemoteDisplayInfo.STATUS_CONNECTING: 1351 case RemoteDisplayInfo.STATUS_AVAILABLE: 1352 return true; 1353 default: 1354 return false; 1355 } 1356 } 1357 1358 private static int computeStatusCode(RemoteDisplayInfo descriptor) { 1359 switch (descriptor.status) { 1360 case RemoteDisplayInfo.STATUS_NOT_AVAILABLE: 1361 return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE; 1362 case RemoteDisplayInfo.STATUS_AVAILABLE: 1363 return MediaRouter.RouteInfo.STATUS_AVAILABLE; 1364 case RemoteDisplayInfo.STATUS_IN_USE: 1365 return MediaRouter.RouteInfo.STATUS_IN_USE; 1366 case RemoteDisplayInfo.STATUS_CONNECTING: 1367 return MediaRouter.RouteInfo.STATUS_CONNECTING; 1368 case RemoteDisplayInfo.STATUS_CONNECTED: 1369 return MediaRouter.RouteInfo.STATUS_CONNECTED; 1370 default: 1371 return MediaRouter.RouteInfo.STATUS_NONE; 1372 } 1373 } 1374 1375 private static int computePlaybackType(RemoteDisplayInfo descriptor) { 1376 return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; 1377 } 1378 1379 private static int computePlaybackStream(RemoteDisplayInfo descriptor) { 1380 return AudioSystem.STREAM_MUSIC; 1381 } 1382 1383 private static int computeVolume(RemoteDisplayInfo descriptor) { 1384 final int volume = descriptor.volume; 1385 final int volumeMax = descriptor.volumeMax; 1386 if (volume < 0) { 1387 return 0; 1388 } else if (volume > volumeMax) { 1389 return volumeMax; 1390 } 1391 return volume; 1392 } 1393 1394 private static int computeVolumeMax(RemoteDisplayInfo descriptor) { 1395 final int volumeMax = descriptor.volumeMax; 1396 return volumeMax > 0 ? volumeMax : 0; 1397 } 1398 1399 private static int computeVolumeHandling(RemoteDisplayInfo descriptor) { 1400 final int volumeHandling = descriptor.volumeHandling; 1401 switch (volumeHandling) { 1402 case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE: 1403 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; 1404 case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED: 1405 default: 1406 return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; 1407 } 1408 } 1409 1410 private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) { 1411 // The MediaRouter class validates that the id corresponds to an extant 1412 // presentation display. So all we do here is canonicalize the null case. 1413 final int displayId = descriptor.presentationDisplayId; 1414 return displayId < 0 ? -1 : displayId; 1415 } 1416 } 1417 } 1418 } 1419