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