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