summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/media/MediaRouterService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/media/MediaRouterService.java')
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java1423
1 files changed, 1423 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
new file mode 100644
index 0000000..f91ea8c
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioSystem;
+import android.media.IMediaRouterClient;
+import android.media.IMediaRouterService;
+import android.media.MediaRouter;
+import android.media.MediaRouterClientState;
+import android.media.RemoteDisplayState;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides a mechanism for discovering media routes and manages media playback
+ * behalf of applications.
+ * <p>
+ * Currently supports discovering remote displays via remote display provider
+ * services that have been registered by applications.
+ * </p>
+ */
+public final class MediaRouterService extends IMediaRouterService.Stub
+ implements Watchdog.Monitor {
+ private static final String TAG = "MediaRouterService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * disconnected state to a connecting state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTING_TIMEOUT = 5000;
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * connecting state to a connected state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTED_TIMEOUT = 60000;
+
+ private final Context mContext;
+
+ // State guarded by mLock.
+ private final Object mLock = new Object();
+ private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+ private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
+ new ArrayMap<IBinder, ClientRecord>();
+ private int mCurrentUserId = -1;
+
+ public MediaRouterService(Context context) {
+ mContext = context;
+ Watchdog.getInstance().addMonitor(this);
+ }
+
+ public void systemRunning() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+ switchUser();
+ }
+ }
+ }, filter);
+
+ switchUser();
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) { /* check for deadlock */ }
+ }
+
+ // Binder call
+ @Override
+ public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final int uid = Binder.getCallingUid();
+ if (!validatePackageName(uid, packageName)) {
+ throw new SecurityException("packageName must match the calling uid");
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+ final boolean trusted = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
+ PackageManager.PERMISSION_GRANTED;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ registerClientLocked(client, pid, packageName, resolvedUserId, trusted);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void unregisterClient(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ unregisterClientLocked(client, false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public MediaRouterClientState getState(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return getStateLocked(client);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void setDiscoveryRequest(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setDiscoveryRequestLocked(client, routeTypes, activeScan);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ // A null routeId means that the client wants to unselect its current route.
+ // The explicit flag indicates whether the change was explicitly requested by the
+ // user or the application which may cause changes to propagate out to the rest
+ // of the system. Should be false when the change is in response to a new globally
+ // selected route or a default selection.
+ @Override
+ public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setSelectedRouteLocked(client, routeId, explicit);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(client, routeId, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(client, routeId, direction);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump MediaRouterService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
+ pw.println();
+ pw.println("Global state");
+ pw.println(" mCurrentUserId=" + mCurrentUserId);
+
+ synchronized (mLock) {
+ final int count = mUserRecords.size();
+ for (int i = 0; i < count; i++) {
+ UserRecord userRecord = mUserRecords.valueAt(i);
+ pw.println();
+ userRecord.dump(pw, "");
+ }
+ }
+ }
+
+ void switchUser() {
+ synchronized (mLock) {
+ int userId = ActivityManager.getCurrentUser();
+ if (mCurrentUserId != userId) {
+ final int oldUserId = mCurrentUserId;
+ mCurrentUserId = userId; // do this first
+
+ UserRecord oldUser = mUserRecords.get(oldUserId);
+ if (oldUser != null) {
+ oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+ disposeUserIfNeededLocked(oldUser); // since no longer current user
+ }
+
+ UserRecord newUser = mUserRecords.get(userId);
+ if (newUser != null) {
+ newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+ }
+ }
+
+ void clientDied(ClientRecord clientRecord) {
+ synchronized (mLock) {
+ unregisterClientLocked(clientRecord.mClient, true);
+ }
+ }
+
+ private void registerClientLocked(IMediaRouterClient client,
+ int pid, String packageName, int userId, boolean trusted) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord == null) {
+ boolean newUser = false;
+ UserRecord userRecord = mUserRecords.get(userId);
+ if (userRecord == null) {
+ userRecord = new UserRecord(userId);
+ newUser = true;
+ }
+ clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted);
+ try {
+ binder.linkToDeath(clientRecord, 0);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Media router client died prematurely.", ex);
+ }
+
+ if (newUser) {
+ mUserRecords.put(userId, userRecord);
+ initializeUserLocked(userRecord);
+ }
+
+ userRecord.mClientRecords.add(clientRecord);
+ mAllClientRecords.put(binder, clientRecord);
+ initializeClientLocked(clientRecord);
+ }
+ }
+
+ private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
+ ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
+ if (clientRecord != null) {
+ UserRecord userRecord = clientRecord.mUserRecord;
+ userRecord.mClientRecords.remove(clientRecord);
+ disposeClientLocked(clientRecord, died);
+ disposeUserIfNeededLocked(userRecord); // since client removed from user
+ }
+ }
+
+ private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ return clientRecord.getState();
+ }
+ return null;
+ }
+
+ private void setDiscoveryRequestLocked(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ // Only let the system discover remote display routes for now.
+ if (!clientRecord.mTrusted) {
+ routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+ }
+
+ if (clientRecord.mRouteTypes != routeTypes
+ || clientRecord.mActiveScan != activeScan) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
+ + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
+ }
+ clientRecord.mRouteTypes = routeTypes;
+ clientRecord.mActiveScan = activeScan;
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ }
+ }
+
+ private void setSelectedRouteLocked(IMediaRouterClient client,
+ String routeId, boolean explicit) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ final String oldRouteId = clientRecord.mSelectedRouteId;
+ if (!Objects.equals(routeId, oldRouteId)) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+ + ", oldRouteId=" + oldRouteId
+ + ", explicit=" + explicit);
+ }
+
+ clientRecord.mSelectedRouteId = routeId;
+ if (explicit) {
+ // Any app can disconnect from the globally selected route.
+ if (oldRouteId != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
+ }
+ // Only let the system connect to new global routes for now.
+ // A similar check exists in the display manager for wifi display.
+ if (routeId != null && clientRecord.mTrusted) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
+ }
+ }
+ }
+ }
+ }
+
+ private void requestSetVolumeLocked(IMediaRouterClient client,
+ String routeId, int volume) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouterClient client,
+ String routeId, int direction) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void initializeUserLocked(UserRecord userRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Initialized");
+ }
+ if (userRecord.mUserId == mCurrentUserId) {
+ userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+
+ private void disposeUserIfNeededLocked(UserRecord userRecord) {
+ // If there are no records left and the user is no longer current then go ahead
+ // and purge the user record and all of its associated state. If the user is current
+ // then leave it alone since we might be connected to a route or want to query
+ // the same route information again soon.
+ if (userRecord.mUserId != mCurrentUserId
+ && userRecord.mClientRecords.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Disposed");
+ }
+ mUserRecords.remove(userRecord.mUserId);
+ // Note: User already stopped (by switchUser) so no need to send stop message here.
+ }
+ }
+
+ private void initializeClientLocked(ClientRecord clientRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Registered");
+ }
+ }
+
+ private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
+ if (DEBUG) {
+ if (died) {
+ Slog.d(TAG, clientRecord + ": Died!");
+ } else {
+ Slog.d(TAG, clientRecord + ": Unregistered");
+ }
+ }
+ if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ clientRecord.dispose();
+ }
+
+ private boolean validatePackageName(int uid, String packageName) {
+ if (packageName != null) {
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packageNames != null) {
+ for (String n : packageNames) {
+ if (n.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Information about a particular client of the media router.
+ * The contents of this object is guarded by mLock.
+ */
+ final class ClientRecord implements DeathRecipient {
+ public final UserRecord mUserRecord;
+ public final IMediaRouterClient mClient;
+ public final int mPid;
+ public final String mPackageName;
+ public final boolean mTrusted;
+
+ public int mRouteTypes;
+ public boolean mActiveScan;
+ public String mSelectedRouteId;
+
+ public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
+ int pid, String packageName, boolean trusted) {
+ mUserRecord = userRecord;
+ mClient = client;
+ mPid = pid;
+ mPackageName = packageName;
+ mTrusted = trusted;
+ }
+
+ public void dispose() {
+ mClient.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ clientDied(this);
+ }
+
+ MediaRouterClientState getState() {
+ return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mTrusted=" + mTrusted);
+ pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
+ pw.println(indent + "mActiveScan=" + mActiveScan);
+ pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
+ }
+
+ @Override
+ public String toString() {
+ return "Client " + mPackageName + " (pid " + mPid + ")";
+ }
+ }
+
+ /**
+ * Information about a particular user.
+ * The contents of this object is guarded by mLock.
+ */
+ final class UserRecord {
+ public final int mUserId;
+ public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
+ public final UserHandler mHandler;
+ public MediaRouterClientState mTrustedState;
+ public MediaRouterClientState mUntrustedState;
+
+ public UserRecord(int userId) {
+ mUserId = userId;
+ mHandler = new UserHandler(MediaRouterService.this, this);
+ }
+
+ public void dump(final PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ final int clientCount = mClientRecords.size();
+ if (clientCount != 0) {
+ for (int i = 0; i < clientCount; i++) {
+ mClientRecords.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no clients>");
+ }
+
+ pw.println(indent + "State");
+ pw.println(indent + "mTrustedState=" + mTrustedState);
+ pw.println(indent + "mUntrustedState=" + mUntrustedState);
+
+ if (!mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.dump(pw, indent);
+ }
+ }, 1000)) {
+ pw.println(indent + "<could not dump handler state>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "User " + mUserId;
+ }
+ }
+
+ /**
+ * Media router handler
+ * <p>
+ * Since remote display providers are designed to be single-threaded by nature,
+ * this class encapsulates all of the associated functionality and exports state
+ * to the service as it evolves.
+ * </p><p>
+ * One important task of this class is to keep track of the current globally selected
+ * route id for certain routes that have global effects, such as remote displays.
+ * Global route selections override local selections made within apps. The change
+ * is propagated to all apps so that they are all in sync. Synchronization works
+ * both ways. Whenever the globally selected route is explicitly unselected by any
+ * app, then it becomes unselected globally and all apps are informed.
+ * </p><p>
+ * This class is currently hardcoded to work with remote display providers but
+ * it is intended to be eventually extended to support more general route providers
+ * similar to the support library media router.
+ * </p>
+ */
+ static final class UserHandler extends Handler
+ implements RemoteDisplayProviderWatcher.Callback,
+ RemoteDisplayProviderProxy.Callback {
+ public static final int MSG_START = 1;
+ public static final int MSG_STOP = 2;
+ public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
+ public static final int MSG_SELECT_ROUTE = 4;
+ public static final int MSG_UNSELECT_ROUTE = 5;
+ public static final int MSG_REQUEST_SET_VOLUME = 6;
+ public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
+ private static final int MSG_UPDATE_CLIENT_STATE = 8;
+ private static final int MSG_CONNECTION_TIMED_OUT = 9;
+
+ private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
+ private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
+
+ // The relative order of these constants is important and expresses progress
+ // through the process of connecting to a route.
+ private static final int PHASE_NOT_AVAILABLE = -1;
+ private static final int PHASE_NOT_CONNECTED = 0;
+ private static final int PHASE_CONNECTING = 1;
+ private static final int PHASE_CONNECTED = 2;
+
+ private final MediaRouterService mService;
+ private final UserRecord mUserRecord;
+ private final RemoteDisplayProviderWatcher mWatcher;
+ private final ArrayList<ProviderRecord> mProviderRecords =
+ new ArrayList<ProviderRecord>();
+ private final ArrayList<IMediaRouterClient> mTempClients =
+ new ArrayList<IMediaRouterClient>();
+
+ private boolean mRunning;
+ private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ private RouteRecord mGloballySelectedRouteRecord;
+ private int mConnectionPhase = PHASE_NOT_AVAILABLE;
+ private int mConnectionTimeoutReason;
+ private long mConnectionTimeoutStartTime;
+ private boolean mClientStateUpdateScheduled;
+
+ public UserHandler(MediaRouterService service, UserRecord userRecord) {
+ super(Looper.getMainLooper(), null, true);
+ mService = service;
+ mUserRecord = userRecord;
+ mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
+ this, mUserRecord.mUserId);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START: {
+ start();
+ break;
+ }
+ case MSG_STOP: {
+ stop();
+ break;
+ }
+ case MSG_UPDATE_DISCOVERY_REQUEST: {
+ updateDiscoveryRequest();
+ break;
+ }
+ case MSG_SELECT_ROUTE: {
+ selectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_UNSELECT_ROUTE: {
+ unselectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_REQUEST_SET_VOLUME: {
+ requestSetVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_REQUEST_UPDATE_VOLUME: {
+ requestUpdateVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_UPDATE_CLIENT_STATE: {
+ updateClientState();
+ break;
+ }
+ case MSG_CONNECTION_TIMED_OUT: {
+ connectionTimedOut();
+ break;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Handler");
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mRunning=" + mRunning);
+ pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
+ pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+ pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
+ pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
+ pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
+ TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
+
+ mWatcher.dump(pw, prefix);
+
+ final int providerCount = mProviderRecords.size();
+ if (providerCount != 0) {
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).dump(pw, prefix);
+ }
+ } else {
+ pw.println(indent + "<no providers>");
+ }
+ }
+
+ private void start() {
+ if (!mRunning) {
+ mRunning = true;
+ mWatcher.start(); // also starts all providers
+ }
+ }
+
+ private void stop() {
+ if (mRunning) {
+ mRunning = false;
+ unselectGloballySelectedRoute();
+ mWatcher.stop(); // also stops all providers
+ }
+ }
+
+ private void updateDiscoveryRequest() {
+ int routeTypes = 0;
+ boolean activeScan = false;
+ synchronized (mService.mLock) {
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
+ routeTypes |= clientRecord.mRouteTypes;
+ activeScan |= clientRecord.mActiveScan;
+ }
+ }
+
+ final int newDiscoveryMode;
+ if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+ if (activeScan) {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+ }
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ }
+
+ if (mDiscoveryMode != newDiscoveryMode) {
+ mDiscoveryMode = newDiscoveryMode;
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
+ }
+ }
+ }
+
+ private void selectRoute(String routeId) {
+ if (routeId != null
+ && (mGloballySelectedRouteRecord == null
+ || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+ RouteRecord routeRecord = findRouteRecord(routeId);
+ if (routeRecord != null) {
+ unselectGloballySelectedRoute();
+
+ Slog.i(TAG, "Selected global route:" + routeRecord);
+ mGloballySelectedRouteRecord = routeRecord;
+ checkGloballySelectedRouteState();
+ routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
+
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ private void unselectRoute(String routeId) {
+ if (routeId != null
+ && mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ unselectGloballySelectedRoute();
+ }
+ }
+
+ private void unselectGloballySelectedRoute() {
+ if (mGloballySelectedRouteRecord != null) {
+ Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
+ mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
+ mGloballySelectedRouteRecord = null;
+ checkGloballySelectedRouteState();
+
+ scheduleUpdateClientState();
+ }
+ }
+
+ private void requestSetVolume(String routeId, int volume) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+ }
+ }
+
+ private void requestUpdateVolume(String routeId, int direction) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+ }
+ }
+
+ @Override
+ public void addProvider(RemoteDisplayProviderProxy provider) {
+ provider.setCallback(this);
+ provider.setDiscoveryMode(mDiscoveryMode);
+ provider.setSelectedDisplay(null); // just to be safe
+
+ ProviderRecord providerRecord = new ProviderRecord(provider);
+ mProviderRecords.add(providerRecord);
+ providerRecord.updateDescriptor(provider.getDisplayState());
+
+ scheduleUpdateClientState();
+ }
+
+ @Override
+ public void removeProvider(RemoteDisplayProviderProxy provider) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.remove(index);
+ providerRecord.updateDescriptor(null); // mark routes invalid
+ provider.setCallback(null);
+ provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
+
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+
+ @Override
+ public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ updateProvider(provider, state);
+ }
+
+ private void updateProvider(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.get(index);
+ if (providerRecord.updateDescriptor(state)) {
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ /**
+ * This function is called whenever the state of the globally selected route
+ * may have changed. It checks the state and updates timeouts or unselects
+ * the route as appropriate.
+ */
+ private void checkGloballySelectedRouteState() {
+ // Unschedule timeouts when the route is unselected.
+ if (mGloballySelectedRouteRecord == null) {
+ mConnectionPhase = PHASE_NOT_AVAILABLE;
+ updateConnectionTimeout(0);
+ return;
+ }
+
+ // Ensure that the route is still present and enabled.
+ if (!mGloballySelectedRouteRecord.isValid()
+ || !mGloballySelectedRouteRecord.isEnabled()) {
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ return;
+ }
+
+ // Make sure we haven't lost our connection.
+ final int oldPhase = mConnectionPhase;
+ mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus());
+ if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
+ updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
+ return;
+ }
+
+ // Check the route status.
+ switch (mConnectionPhase) {
+ case PHASE_CONNECTED:
+ if (oldPhase != PHASE_CONNECTED) {
+ Slog.i(TAG, "Connected to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(0);
+ break;
+ case PHASE_CONNECTING:
+ if (oldPhase != PHASE_CONNECTING) {
+ Slog.i(TAG, "Connecting to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
+ break;
+ case PHASE_NOT_CONNECTED:
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
+ break;
+ case PHASE_NOT_AVAILABLE:
+ default:
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ break;
+ }
+ }
+
+ private void updateConnectionTimeout(int reason) {
+ if (reason != mConnectionTimeoutReason) {
+ if (mConnectionTimeoutReason != 0) {
+ removeMessages(MSG_CONNECTION_TIMED_OUT);
+ }
+ mConnectionTimeoutReason = reason;
+ mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
+ switch (reason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ case TIMEOUT_REASON_CONNECTION_LOST:
+ // Route became unavailable or connection lost.
+ // Unselect it immediately.
+ sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ // Waiting for route to start connecting.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ // Waiting for route to complete connection.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
+ break;
+ }
+ }
+ }
+
+ private void connectionTimedOut() {
+ if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+ // Shouldn't get here. There must be a bug somewhere.
+ Log.wtf(TAG, "Handled connection timeout for no reason.");
+ return;
+ }
+
+ switch (mConnectionTimeoutReason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ Slog.i(TAG, "Global route no longer available: "
+ + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_CONNECTION_LOST:
+ Slog.i(TAG, "Global route connection lost: "
+ + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ Slog.i(TAG, "Global route timed out while waiting for "
+ + "connection attempt to begin after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ Slog.i(TAG, "Global route timed out while connecting after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ }
+ mConnectionTimeoutReason = 0;
+
+ unselectGloballySelectedRoute();
+ }
+
+ private void scheduleUpdateClientState() {
+ if (!mClientStateUpdateScheduled) {
+ mClientStateUpdateScheduled = true;
+ sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
+ }
+ }
+
+ private void updateClientState() {
+ mClientStateUpdateScheduled = false;
+
+ final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
+ mGloballySelectedRouteRecord.getUniqueId() : null;
+
+ // Build a new client state for trusted clients.
+ MediaRouterClientState trustedState = new MediaRouterClientState();
+ trustedState.globallySelectedRouteId = globallySelectedRouteId;
+ final int providerCount = mProviderRecords.size();
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).appendClientState(trustedState);
+ }
+
+ // Build a new client state for untrusted clients that can only see
+ // the currently selected route.
+ MediaRouterClientState untrustedState = new MediaRouterClientState();
+ untrustedState.globallySelectedRouteId = globallySelectedRouteId;
+ if (globallySelectedRouteId != null) {
+ untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId));
+ }
+
+ try {
+ synchronized (mService.mLock) {
+ // Update the UserRecord.
+ mUserRecord.mTrustedState = trustedState;
+ mUserRecord.mUntrustedState = untrustedState;
+
+ // Collect all clients.
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
+ }
+ }
+
+ // Notify all clients (outside of the lock).
+ final int count = mTempClients.size();
+ for (int i = 0; i < count; i++) {
+ try {
+ mTempClients.get(i).onStateChanged();
+ } catch (RemoteException ex) {
+ // ignore errors, client probably died
+ }
+ }
+ } finally {
+ // Clear the list in preparation for the next time.
+ mTempClients.clear();
+ }
+ }
+
+ private int findProviderRecord(RemoteDisplayProviderProxy provider) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ ProviderRecord record = mProviderRecords.get(i);
+ if (record.getProvider() == provider) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private RouteRecord findRouteRecord(String uniqueId) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
+ if (record != null) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private static int getConnectionPhase(int status) {
+ switch (status) {
+ case MediaRouter.RouteInfo.STATUS_NONE:
+ case MediaRouter.RouteInfo.STATUS_CONNECTED:
+ return PHASE_CONNECTED;
+ case MediaRouter.RouteInfo.STATUS_CONNECTING:
+ return PHASE_CONNECTING;
+ case MediaRouter.RouteInfo.STATUS_SCANNING:
+ case MediaRouter.RouteInfo.STATUS_AVAILABLE:
+ return PHASE_NOT_CONNECTED;
+ case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
+ case MediaRouter.RouteInfo.STATUS_IN_USE:
+ default:
+ return PHASE_NOT_AVAILABLE;
+ }
+ }
+
+ static final class ProviderRecord {
+ private final RemoteDisplayProviderProxy mProvider;
+ private final String mUniquePrefix;
+ private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
+ private RemoteDisplayState mDescriptor;
+
+ public ProviderRecord(RemoteDisplayProviderProxy provider) {
+ mProvider = provider;
+ mUniquePrefix = provider.getFlattenedComponentName() + ":";
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProvider;
+ }
+
+ public String getUniquePrefix() {
+ return mUniquePrefix;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayState descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+
+ // Update all existing routes and reorder them to match
+ // the order of their descriptors.
+ int targetIndex = 0;
+ if (descriptor != null) {
+ if (descriptor.isValid()) {
+ final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
+ final int routeCount = routeDescriptors.size();
+ for (int i = 0; i < routeCount; i++) {
+ final RemoteDisplayInfo routeDescriptor =
+ routeDescriptors.get(i);
+ final String descriptorId = routeDescriptor.id;
+ final int sourceIndex = findRouteByDescriptorId(descriptorId);
+ if (sourceIndex < 0) {
+ // Add the route to the provider.
+ String uniqueId = assignRouteUniqueId(descriptorId);
+ RouteRecord route =
+ new RouteRecord(this, descriptorId, uniqueId);
+ mRoutes.add(targetIndex++, route);
+ route.updateDescriptor(routeDescriptor);
+ changed = true;
+ } else if (sourceIndex < targetIndex) {
+ // Ignore route with duplicate id.
+ Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
+ + routeDescriptor);
+ } else {
+ // Reorder existing route within the list.
+ RouteRecord route = mRoutes.get(sourceIndex);
+ Collections.swap(mRoutes, sourceIndex, targetIndex++);
+ changed |= route.updateDescriptor(routeDescriptor);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
+ + mProvider.getFlattenedComponentName());
+ }
+ }
+
+ // Dispose all remaining routes that do not have matching descriptors.
+ for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
+ RouteRecord route = mRoutes.remove(i);
+ route.updateDescriptor(null); // mark route invalid
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public void appendClientState(MediaRouterClientState state) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ state.routes.add(mRoutes.get(i).getInfo());
+ }
+ }
+
+ public RouteRecord findRouteByUniqueId(String uniqueId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getUniqueId().equals(uniqueId)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
+ private int findRouteByDescriptorId(String descriptorId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getDescriptorId().equals(descriptorId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ mProvider.dump(pw, indent);
+
+ final int routeCount = mRoutes.size();
+ if (routeCount != 0) {
+ for (int i = 0; i < routeCount; i++) {
+ mRoutes.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no routes>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Provider " + mProvider.getFlattenedComponentName();
+ }
+
+ private String assignRouteUniqueId(String descriptorId) {
+ return mUniquePrefix + descriptorId;
+ }
+ }
+
+ static final class RouteRecord {
+ private final ProviderRecord mProviderRecord;
+ private final String mDescriptorId;
+ private final MediaRouterClientState.RouteInfo mMutableInfo;
+ private MediaRouterClientState.RouteInfo mImmutableInfo;
+ private RemoteDisplayInfo mDescriptor;
+
+ public RouteRecord(ProviderRecord providerRecord,
+ String descriptorId, String uniqueId) {
+ mProviderRecord = providerRecord;
+ mDescriptorId = descriptorId;
+ mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProviderRecord.getProvider();
+ }
+
+ public ProviderRecord getProviderRecord() {
+ return mProviderRecord;
+ }
+
+ public String getDescriptorId() {
+ return mDescriptorId;
+ }
+
+ public String getUniqueId() {
+ return mMutableInfo.id;
+ }
+
+ public MediaRouterClientState.RouteInfo getInfo() {
+ if (mImmutableInfo == null) {
+ mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
+ }
+ return mImmutableInfo;
+ }
+
+ public boolean isValid() {
+ return mDescriptor != null;
+ }
+
+ public boolean isEnabled() {
+ return mMutableInfo.enabled;
+ }
+
+ public int getStatus() {
+ return mMutableInfo.statusCode;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ final String name = computeName(descriptor);
+ if (!Objects.equals(mMutableInfo.name, name)) {
+ mMutableInfo.name = name;
+ changed = true;
+ }
+ final String description = computeDescription(descriptor);
+ if (!Objects.equals(mMutableInfo.description, description)) {
+ mMutableInfo.description = description;
+ changed = true;
+ }
+ final int supportedTypes = computeSupportedTypes(descriptor);
+ if (mMutableInfo.supportedTypes != supportedTypes) {
+ mMutableInfo.supportedTypes = supportedTypes;
+ changed = true;
+ }
+ final boolean enabled = computeEnabled(descriptor);
+ if (mMutableInfo.enabled != enabled) {
+ mMutableInfo.enabled = enabled;
+ changed = true;
+ }
+ final int statusCode = computeStatusCode(descriptor);
+ if (mMutableInfo.statusCode != statusCode) {
+ mMutableInfo.statusCode = statusCode;
+ changed = true;
+ }
+ final int playbackType = computePlaybackType(descriptor);
+ if (mMutableInfo.playbackType != playbackType) {
+ mMutableInfo.playbackType = playbackType;
+ changed = true;
+ }
+ final int playbackStream = computePlaybackStream(descriptor);
+ if (mMutableInfo.playbackStream != playbackStream) {
+ mMutableInfo.playbackStream = playbackStream;
+ changed = true;
+ }
+ final int volume = computeVolume(descriptor);
+ if (mMutableInfo.volume != volume) {
+ mMutableInfo.volume = volume;
+ changed = true;
+ }
+ final int volumeMax = computeVolumeMax(descriptor);
+ if (mMutableInfo.volumeMax != volumeMax) {
+ mMutableInfo.volumeMax = volumeMax;
+ changed = true;
+ }
+ final int volumeHandling = computeVolumeHandling(descriptor);
+ if (mMutableInfo.volumeHandling != volumeHandling) {
+ mMutableInfo.volumeHandling = volumeHandling;
+ changed = true;
+ }
+ final int presentationDisplayId = computePresentationDisplayId(descriptor);
+ if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+ mMutableInfo.presentationDisplayId = presentationDisplayId;
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mImmutableInfo = null;
+ }
+ return changed;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mMutableInfo=" + mMutableInfo);
+ pw.println(indent + "mDescriptorId=" + mDescriptorId);
+ pw.println(indent + "mDescriptor=" + mDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
+ }
+
+ private static String computeName(RemoteDisplayInfo descriptor) {
+ // Note that isValid() already ensures the name is non-empty.
+ return descriptor.name;
+ }
+
+ private static String computeDescription(RemoteDisplayInfo descriptor) {
+ final String description = descriptor.description;
+ return TextUtils.isEmpty(description) ? null : description;
+ }
+
+ private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
+ return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+ | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+ | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+ }
+
+ private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static int computeStatusCode(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_IN_USE:
+ return MediaRouter.RouteInfo.STATUS_IN_USE;
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ return MediaRouter.RouteInfo.STATUS_CONNECTING;
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ return MediaRouter.RouteInfo.STATUS_CONNECTED;
+ default:
+ return MediaRouter.RouteInfo.STATUS_NONE;
+ }
+ }
+
+ private static int computePlaybackType(RemoteDisplayInfo descriptor) {
+ return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
+ return AudioSystem.STREAM_MUSIC;
+ }
+
+ private static int computeVolume(RemoteDisplayInfo descriptor) {
+ final int volume = descriptor.volume;
+ final int volumeMax = descriptor.volumeMax;
+ if (volume < 0) {
+ return 0;
+ } else if (volume > volumeMax) {
+ return volumeMax;
+ }
+ return volume;
+ }
+
+ private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
+ final int volumeMax = descriptor.volumeMax;
+ return volumeMax > 0 ? volumeMax : 0;
+ }
+
+ private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
+ final int volumeHandling = descriptor.volumeHandling;
+ switch (volumeHandling) {
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
+ default:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+ }
+ }
+
+ private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
+ // The MediaRouter class validates that the id corresponds to an extant
+ // presentation display. So all we do here is canonicalize the null case.
+ final int displayId = descriptor.presentationDisplayId;
+ return displayId < 0 ? -1 : displayId;
+ }
+ }
+ }
+}