Merge "Add media router service and integrate with remote displays." into klp-dev

This commit is contained in:
Jeff Brown
2013-11-08 01:36:55 +00:00
committed by Android (Google) Code Review
22 changed files with 3071 additions and 109 deletions

View File

@ -251,6 +251,8 @@ LOCAL_SRC_FILES += \
media/java/android/media/IAudioService.aidl \
media/java/android/media/IAudioFocusDispatcher.aidl \
media/java/android/media/IAudioRoutesObserver.aidl \
media/java/android/media/IMediaRouterClient.aidl \
media/java/android/media/IMediaRouterService.aidl \
media/java/android/media/IMediaScannerListener.aidl \
media/java/android/media/IMediaScannerService.aidl \
media/java/android/media/IRemoteControlClient.aidl \

View File

@ -60,7 +60,7 @@ public class MediaRouteActionProvider extends ActionProvider {
}
mRouteTypes = types;
if (types != 0) {
mRouter.addCallback(types, mCallback);
mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
}
if (mView != null) {
mView.setRouteTypes(mRouteTypes);

View File

@ -123,14 +123,14 @@ public class MediaRouteButton extends View {
if (mToggleMode) {
if (mRemoteActive) {
mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute());
mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true);
} else {
final int N = mRouter.getRouteCount();
for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i);
if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
route != mRouter.getDefaultRoute()) {
mRouter.selectRouteInt(mRouteTypes, route);
mRouter.selectRouteInt(mRouteTypes, route, true);
}
}
}
@ -201,7 +201,8 @@ public class MediaRouteButton extends View {
if (mAttachedToWindow) {
updateRouteInfo();
mRouter.addCallback(types, mRouterCallback);
mRouter.addCallback(types, mRouterCallback,
MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
}
}
@ -217,8 +218,7 @@ public class MediaRouteButton extends View {
void updateRemoteIndicator() {
final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
final boolean isRemote = selected != mRouter.getDefaultRoute();
final boolean isConnecting = selected != null &&
selected.getStatusCode() == RouteInfo.STATUS_CONNECTING;
final boolean isConnecting = selected != null && selected.isConnecting();
boolean needsRefresh = false;
if (mRemoteActive != isRemote) {
@ -238,7 +238,7 @@ public class MediaRouteButton extends View {
void updateRouteCount() {
final int N = mRouter.getRouteCount();
int count = 0;
boolean hasVideoRoutes = false;
boolean scanRequired = false;
for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i);
final int routeTypes = route.getSupportedTypes();
@ -248,8 +248,9 @@ public class MediaRouteButton extends View {
} else {
count++;
}
if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
hasVideoRoutes = true;
if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO
| MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
scanRequired = true;
}
}
}
@ -257,9 +258,10 @@ public class MediaRouteButton extends View {
setEnabled(count != 0);
// Only allow toggling if we have more than just user routes.
// Don't toggle if we support video routes, we may have to let the dialog scan.
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
!hasVideoRoutes;
// Don't toggle if we support video or remote display routes, we may have to
// let the dialog scan.
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0
&& !scanRequired;
}
@Override
@ -313,7 +315,8 @@ public class MediaRouteButton extends View {
super.onAttachedToWindow();
mAttachedToWindow = true;
if (mRouteTypes != 0) {
mRouter.addCallback(mRouteTypes, mRouterCallback);
mRouter.addCallback(mRouteTypes, mRouterCallback,
MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
updateRouteInfo();
}
}

View File

@ -643,6 +643,15 @@ public final class Display {
|| uid == 0;
}
/**
* Returns true if the display is a public presentation display.
* @hide
*/
public boolean isPublicPresentation() {
return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) ==
Display.FLAG_PRESENTATION;
}
private void updateDisplayInfoLocked() {
// Note: The display manager caches display info objects on our behalf.
DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);

View File

@ -501,7 +501,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
final RouteInfo route = (RouteInfo) item;
if (type == VIEW_ROUTE) {
mRouter.selectRouteInt(mRouteTypes, route);
mRouter.selectRouteInt(mRouteTypes, route, true);
dismiss();
} else if (type == VIEW_GROUPING_ROUTE) {
final Checkable c = (Checkable) view;
@ -514,7 +514,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
// Old group was selected but is now empty. Select the group
// we're manipulating since that's where the last route went.
mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
}
oldGroup.removeRoute(route);
mEditingGroup.addRoute(route);
@ -555,7 +555,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
mEditingGroup = group;
mCategoryEditingGroups = group.getCategory();
getDialog().setCanceledOnTouchOutside(false);
mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
update();
scrollToEditingGroup();
}

View File

@ -0,0 +1,24 @@
/*
* 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 android.media;
/**
* {@hide}
*/
oneway interface IMediaRouterClient {
void onStateChanged();
}

View File

@ -0,0 +1,35 @@
/*
* 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 android.media;
import android.media.IMediaRouterClient;
import android.media.MediaRouterClientState;
/**
* {@hide}
*/
interface IMediaRouterService {
void registerClientAsUser(IMediaRouterClient client, String packageName, int userId);
void unregisterClient(IMediaRouterClient client);
MediaRouterClientState getState(IMediaRouterClient client);
void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
void requestSetVolume(IMediaRouterClient client, String routeId, int volume);
void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction);
}

View File

@ -16,6 +16,8 @@
package android.media;
import com.android.internal.util.Objects;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -30,6 +32,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
@ -52,14 +55,17 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public class MediaRouter {
private static final String TAG = "MediaRouter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static class Static implements DisplayManager.DisplayListener {
// Time between wifi display scans when actively scanning in milliseconds.
private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
final Context mAppContext;
final Resources mResources;
final IAudioService mAudioService;
final DisplayManager mDisplayService;
final IMediaRouterService mMediaRouterService;
final Handler mHandler;
final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
new CopyOnWriteArrayList<CallbackInfo>();
@ -79,6 +85,13 @@ public class MediaRouter {
WifiDisplayStatus mLastKnownWifiDisplayStatus;
boolean mActivelyScanningWifiDisplays;
int mDiscoveryRequestRouteTypes;
boolean mDiscoverRequestActiveScan;
int mCurrentUserId = -1;
IMediaRouterClient mClient;
MediaRouterClientState mClientState;
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@Override
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
@ -101,6 +114,7 @@ public class MediaRouter {
};
Static(Context appContext) {
mAppContext = appContext;
mResources = Resources.getSystem();
mHandler = new Handler(appContext.getMainLooper());
@ -109,6 +123,9 @@ public class MediaRouter {
mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mSystemCategory = new RouteCategory(
com.android.internal.R.string.default_audio_route_category_name,
ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
@ -146,10 +163,13 @@ public class MediaRouter {
updateAudioRoutes(newAudioRoutes);
}
// Bind to the media router service.
rebindAsUser(UserHandle.myUserId());
// Select the default route if the above didn't sync us up
// appropriately with relevant system state.
if (mSelectedRoute == null) {
selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo);
selectDefaultRouteStatic();
}
}
@ -197,7 +217,7 @@ public class MediaRouter {
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
removeRoute(sStatic.mBluetoothA2dpRoute);
removeRouteStatic(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
}
@ -205,16 +225,52 @@ public class MediaRouter {
if (mBluetoothA2dpRoute != null) {
if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
} else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
}
}
}
void updateActiveScan() {
if (hasActiveScanCallbackOfType(ROUTE_TYPE_LIVE_VIDEO)) {
void updateDiscoveryRequest() {
// What are we looking for today?
int routeTypes = 0;
int passiveRouteTypes = 0;
boolean activeScan = false;
boolean activeScanWifiDisplay = false;
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
| CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) {
// Discovery explicitly requested.
routeTypes |= cbi.type;
} else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) {
// Discovery only passively requested.
passiveRouteTypes |= cbi.type;
} else {
// Legacy case since applications don't specify the discovery flag.
// Unfortunately we just have to assume they always need discovery
// whenever they have a callback registered.
routeTypes |= cbi.type;
}
if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
activeScan = true;
if ((cbi.type & (ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
activeScanWifiDisplay = true;
}
}
}
if (routeTypes != 0 || activeScan) {
// If someone else requests discovery then enable the passive listeners.
// This is used by the MediaRouteButton and MediaRouteActionProvider since
// they don't receive lifecycle callbacks from the Activity.
routeTypes |= passiveRouteTypes;
}
// Update wifi display scanning.
if (activeScanWifiDisplay) {
if (!mActivelyScanningWifiDisplays) {
mActivelyScanningWifiDisplays = true;
mHandler.post(mScanWifiDisplays);
@ -225,18 +281,14 @@ public class MediaRouter {
mHandler.removeCallbacks(mScanWifiDisplays);
}
}
}
private boolean hasActiveScanCallbackOfType(int type) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0
&& (cbi.type & type) != 0) {
return true;
}
// Tell the media router service all about it.
if (routeTypes != mDiscoveryRequestRouteTypes
|| activeScan != mDiscoverRequestActiveScan) {
mDiscoveryRequestRouteTypes = routeTypes;
mDiscoverRequestActiveScan = activeScan;
publishClientDiscoveryRequest();
}
return false;
}
@Override
@ -271,6 +323,270 @@ public class MediaRouter {
}
}
}
void setSelectedRoute(RouteInfo info, boolean explicit) {
// Must be non-reentrant.
mSelectedRoute = info;
publishClientSelectedRoute(explicit);
}
void rebindAsUser(int userId) {
if (mCurrentUserId != userId || userId < 0 || mClient == null) {
if (mClient != null) {
try {
mMediaRouterService.unregisterClient(mClient);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to unregister media router client.", ex);
}
mClient = null;
}
mCurrentUserId = userId;
try {
Client client = new Client();
mMediaRouterService.registerClientAsUser(client,
mAppContext.getPackageName(), userId);
mClient = client;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router client.", ex);
}
publishClientDiscoveryRequest();
publishClientSelectedRoute(false);
updateClientState();
}
}
void publishClientDiscoveryRequest() {
if (mClient != null) {
try {
mMediaRouterService.setDiscoveryRequest(mClient,
mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to publish media router client discovery request.", ex);
}
}
}
void publishClientSelectedRoute(boolean explicit) {
if (mClient != null) {
try {
mMediaRouterService.setSelectedRoute(mClient,
mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null,
explicit);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to publish media router client selected route.", ex);
}
}
}
void updateClientState() {
// Update the client state.
mClientState = null;
if (mClient != null) {
try {
mClientState = mMediaRouterService.getState(mClient);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to retrieve media router client state.", ex);
}
}
final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes =
mClientState != null ? mClientState.routes : null;
final String globallySelectedRouteId = mClientState != null ?
mClientState.globallySelectedRouteId : null;
// Add or update routes.
final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0;
for (int i = 0; i < globalRouteCount; i++) {
final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i);
RouteInfo route = findGlobalRoute(globalRoute.id);
if (route == null) {
route = makeGlobalRoute(globalRoute);
addRouteStatic(route);
} else {
updateGlobalRoute(route, globalRoute);
}
}
// Synchronize state with the globally selected route.
if (globallySelectedRouteId != null) {
final RouteInfo route = findGlobalRoute(globallySelectedRouteId);
if (route == null) {
Log.w(TAG, "Could not find new globally selected route: "
+ globallySelectedRouteId);
} else if (route != mSelectedRoute) {
if (DEBUG) {
Log.d(TAG, "Selecting new globally selected route: " + route);
}
selectRouteStatic(route.mSupportedTypes, route, false);
}
} else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) {
if (DEBUG) {
Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute);
}
selectDefaultRouteStatic();
}
// Remove defunct routes.
outer: for (int i = mRoutes.size(); i-- > 0; ) {
final RouteInfo route = mRoutes.get(i);
final String globalRouteId = route.mGlobalRouteId;
if (globalRouteId != null) {
for (int j = 0; j < globalRouteCount; j++) {
MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j);
if (globalRouteId.equals(globalRoute.id)) {
continue outer; // found
}
}
// not found
removeRouteStatic(route);
}
}
}
void requestSetVolume(RouteInfo route, int volume) {
if (route.mGlobalRouteId != null && mClient != null) {
try {
mMediaRouterService.requestSetVolume(mClient,
route.mGlobalRouteId, volume);
} catch (RemoteException ex) {
Log.w(TAG, "Unable to request volume change.", ex);
}
}
}
void requestUpdateVolume(RouteInfo route, int direction) {
if (route.mGlobalRouteId != null && mClient != null) {
try {
mMediaRouterService.requestUpdateVolume(mClient,
route.mGlobalRouteId, direction);
} catch (RemoteException ex) {
Log.w(TAG, "Unable to request volume change.", ex);
}
}
}
RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
RouteInfo route = new RouteInfo(sStatic.mSystemCategory);
route.mGlobalRouteId = globalRoute.id;
route.mName = globalRoute.name;
route.mDescription = globalRoute.description;
route.mSupportedTypes = globalRoute.supportedTypes;
route.mEnabled = globalRoute.enabled;
route.setStatusCode(globalRoute.statusCode);
route.mPlaybackType = globalRoute.playbackType;
route.mPlaybackStream = globalRoute.playbackStream;
route.mVolume = globalRoute.volume;
route.mVolumeMax = globalRoute.volumeMax;
route.mVolumeHandling = globalRoute.volumeHandling;
route.mPresentationDisplay = getDisplayForGlobalRoute(globalRoute);
return route;
}
void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) {
boolean changed = false;
boolean volumeChanged = false;
boolean presentationDisplayChanged = false;
if (!Objects.equal(route.mName, globalRoute.name)) {
route.mName = globalRoute.name;
changed = true;
}
if (!Objects.equal(route.mDescription, globalRoute.description)) {
route.mDescription = globalRoute.description;
changed = true;
}
if (route.mSupportedTypes != globalRoute.supportedTypes) {
route.mSupportedTypes = globalRoute.supportedTypes;
changed = true;
}
if (route.mEnabled != globalRoute.enabled) {
route.mEnabled = globalRoute.enabled;
changed = true;
}
if (route.mStatusCode != globalRoute.statusCode) {
route.setStatusCode(globalRoute.statusCode);
changed = true;
}
if (route.mPlaybackType != globalRoute.playbackType) {
route.mPlaybackType = globalRoute.playbackType;
changed = true;
}
if (route.mPlaybackStream != globalRoute.playbackStream) {
route.mPlaybackStream = globalRoute.playbackStream;
changed = true;
}
if (route.mVolume != globalRoute.volume) {
route.mVolume = globalRoute.volume;
changed = true;
volumeChanged = true;
}
if (route.mVolumeMax != globalRoute.volumeMax) {
route.mVolumeMax = globalRoute.volumeMax;
changed = true;
volumeChanged = true;
}
if (route.mVolumeHandling != globalRoute.volumeHandling) {
route.mVolumeHandling = globalRoute.volumeHandling;
changed = true;
volumeChanged = true;
}
final Display presentationDisplay = getDisplayForGlobalRoute(globalRoute);
if (route.mPresentationDisplay != presentationDisplay) {
route.mPresentationDisplay = presentationDisplay;
changed = true;
presentationDisplayChanged = true;
}
if (changed) {
dispatchRouteChanged(route);
}
if (volumeChanged) {
dispatchRouteVolumeChanged(route);
}
if (presentationDisplayChanged) {
dispatchRoutePresentationDisplayChanged(route);
}
}
Display getDisplayForGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) {
// Ensure that the specified display is valid for presentations.
// This check will normally disallow the default display unless it was configured
// as a presentation display for some reason.
if (globalRoute.presentationDisplayId >= 0) {
Display display = mDisplayService.getDisplay(globalRoute.presentationDisplayId);
if (display != null && display.isPublicPresentation()) {
return display;
}
}
return null;
}
RouteInfo findGlobalRoute(String globalRouteId) {
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
final RouteInfo route = mRoutes.get(i);
if (globalRouteId.equals(route.mGlobalRouteId)) {
return route;
}
}
return null;
}
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (Client.this == mClient) {
updateClientState();
}
}
});
}
}
}
static Static sStatic;
@ -285,7 +601,7 @@ public class MediaRouter {
* <p>Once initiated this routing is transparent to the application. All audio
* played on the media stream will be routed to the selected destination.</p>
*/
public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0;
/**
* Route type flag for live video.
@ -302,7 +618,13 @@ public class MediaRouter {
* @see RouteInfo#getPresentationDisplay()
* @see android.app.Presentation
*/
public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1;
/**
* Temporary interop constant to identify remote displays.
* @hide To be removed when media router API is updated.
*/
public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2;
/**
* Route type flag for application-specific usage.
@ -312,7 +634,10 @@ public class MediaRouter {
* is expected to interpret the meaning of these events and perform the requested
* routing tasks.</p>
*/
public static final int ROUTE_TYPE_USER = 0x00800000;
public static final int ROUTE_TYPE_USER = 1 << 23;
static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
| ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER;
/**
* Flag for {@link #addCallback}: Actively scan for routes while this callback
@ -336,11 +661,27 @@ public class MediaRouter {
* Flag for {@link #addCallback}: Do not filter route events.
* <p>
* When this flag is specified, the callback will be invoked for event that affect any
* route event if they do not match the callback's associated media route selector.
* route even if they do not match the callback's filter.
* </p>
*/
public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
/**
* Explicitly requests discovery.
*
* @hide Future API ported from support library. Revisit this later.
*/
public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
/**
* Requests that discovery be performed but only if there is some other active
* callback already registered.
*
* @hide Compatibility workaround for the fact that applications do not currently
* request discovery explicitly (except when using the support library API).
*/
public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3;
// Maps application contexts
static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
@ -352,6 +693,9 @@ public class MediaRouter {
if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
result.append("ROUTE_TYPE_LIVE_VIDEO ");
}
if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
result.append("ROUTE_TYPE_REMOTE_DISPLAY ");
}
if ((types & ROUTE_TYPE_USER) != 0) {
result.append("ROUTE_TYPE_USER ");
}
@ -453,9 +797,7 @@ public class MediaRouter {
info = new CallbackInfo(cb, types, flags, this);
sStatic.mCallbacks.add(info);
}
if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
sStatic.updateActiveScan();
}
sStatic.updateDiscoveryRequest();
}
/**
@ -466,10 +808,8 @@ public class MediaRouter {
public void removeCallback(Callback cb) {
int index = findCallbackInfo(cb);
if (index >= 0) {
CallbackInfo info = sStatic.mCallbacks.remove(index);
if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
sStatic.updateActiveScan();
}
sStatic.mCallbacks.remove(index);
sStatic.updateDiscoveryRequest();
} else {
Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
}
@ -499,17 +839,17 @@ public class MediaRouter {
* @param route Route to select
*/
public void selectRoute(int types, RouteInfo route) {
selectRouteStatic(types, route);
selectRouteStatic(types, route, true);
}
/**
* @hide internal use
*/
public void selectRouteInt(int types, RouteInfo route) {
selectRouteStatic(types, route);
public void selectRouteInt(int types, RouteInfo route, boolean explicit) {
selectRouteStatic(types, route, explicit);
}
static void selectRouteStatic(int types, RouteInfo route) {
static void selectRouteStatic(int types, RouteInfo route, boolean explicit) {
final RouteInfo oldRoute = sStatic.mSelectedRoute;
if (oldRoute == route) return;
if ((route.getSupportedTypes() & types) == 0) {
@ -541,15 +881,26 @@ public class MediaRouter {
}
}
sStatic.setSelectedRoute(route, explicit);
if (oldRoute != null) {
dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
}
sStatic.mSelectedRoute = route;
if (route != null) {
dispatchRouteSelected(types & route.getSupportedTypes(), route);
}
}
static void selectDefaultRouteStatic() {
// TODO: Be smarter about the route types here; this selects for all valid.
if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
&& sStatic.mBluetoothA2dpRoute != null) {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
} else {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
}
}
/**
* Compare the device address of a display and a route.
* Nulls/no device address will match another null/no address.
@ -612,7 +963,7 @@ public class MediaRouter {
* @see #addUserRoute(UserRouteInfo)
*/
public void removeUserRoute(UserRouteInfo info) {
removeRoute(info);
removeRouteStatic(info);
}
/**
@ -626,7 +977,7 @@ public class MediaRouter {
// TODO Right now, RouteGroups only ever contain user routes.
// The code below will need to change if this assumption does.
if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
removeRouteAt(i);
removeRouteStatic(info);
i--;
}
}
@ -636,10 +987,10 @@ public class MediaRouter {
* @hide internal use only
*/
public void removeRouteInt(RouteInfo info) {
removeRoute(info);
removeRouteStatic(info);
}
static void removeRoute(RouteInfo info) {
static void removeRouteStatic(RouteInfo info) {
if (sStatic.mRoutes.remove(info)) {
final RouteCategory removingCat = info.getCategory();
final int count = sStatic.mRoutes.size();
@ -653,40 +1004,7 @@ public class MediaRouter {
}
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER,
sStatic.mBluetoothA2dpRoute);
} else {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER,
sStatic.mDefaultAudioVideo);
}
}
if (!found) {
sStatic.mCategories.remove(removingCat);
}
dispatchRouteRemoved(info);
}
}
void removeRouteAt(int routeIndex) {
if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
final RouteCategory removingCat = info.getCategory();
final int count = sStatic.mRoutes.size();
boolean found = false;
for (int i = 0; i < count; i++) {
final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
if (removingCat == cat) {
found = true;
break;
}
}
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
sStatic.mDefaultAudioVideo);
selectDefaultRouteStatic();
}
if (!found) {
sStatic.mCategories.remove(removingCat);
@ -752,7 +1070,7 @@ public class MediaRouter {
*
* @see #addUserRoute(UserRouteInfo)
* @see #removeUserRoute(UserRouteInfo)
* @see #createRouteCategory(CharSequence)
* @see #createRouteCategory(CharSequence, boolean)
*/
public UserRouteInfo createUserRoute(RouteCategory category) {
return new UserRouteInfo(category);
@ -780,6 +1098,23 @@ public class MediaRouter {
return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
}
/**
* Rebinds the media router to handle routes that belong to the specified user.
* Requires the interact across users permission to access the routes of another user.
* <p>
* This method is a complete hack to work around the singleton nature of the
* media router when running inside of singleton processes like QuickSettings.
* This mechanism should be burned to the ground when MediaRouter is redesigned.
* Ideally the current user would be pulled from the Context but we need to break
* down MediaRouter.Static before we can get there.
* </p>
*
* @hide
*/
public void rebindAsUser(int userId) {
sStatic.rebindAsUser(userId);
}
static void updateRoute(final RouteInfo info) {
dispatchRouteChanged(info);
}
@ -906,7 +1241,7 @@ public class MediaRouter {
updateWifiDisplayRoute(route, d, newStatus);
}
if (d.equals(activeDisplay)) {
selectRouteStatic(route.getSupportedTypes(), route);
selectRouteStatic(route.getSupportedTypes(), route, false);
// Don't scan if we're already connected to a wifi display,
// the scanning process can cause a hiccup with some configurations.
@ -919,7 +1254,7 @@ public class MediaRouter {
if (d.isRemembered()) {
final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
if (newDisplay == null || !newDisplay.isRemembered()) {
removeRoute(findWifiDisplayRoute(d));
removeRouteStatic(findWifiDisplayRoute(d));
}
}
}
@ -932,8 +1267,7 @@ public class MediaRouter {
}
static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) {
int newStatus = RouteInfo.STATUS_NONE;
int newStatus;
if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
newStatus = RouteInfo.STATUS_SCANNING;
} else if (d.isAvailable()) {
@ -947,7 +1281,7 @@ public class MediaRouter {
final int activeState = wfdStatus.getActiveDisplayState();
switch (activeState) {
case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
newStatus = RouteInfo.STATUS_NONE;
newStatus = RouteInfo.STATUS_CONNECTED;
break;
case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
newStatus = RouteInfo.STATUS_CONNECTING;
@ -968,7 +1302,8 @@ public class MediaRouter {
static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
newRoute.mDeviceAddress = display.getDeviceAddress();
newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO
| ROUTE_TYPE_REMOTE_DISPLAY;
newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
@ -1004,8 +1339,7 @@ public class MediaRouter {
if (!enabled && route == sStatic.mSelectedRoute) {
// Oops, no longer available. Reselect the default.
final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
selectDefaultRouteStatic();
}
}
@ -1075,6 +1409,10 @@ public class MediaRouter {
String mDeviceAddress;
boolean mEnabled = true;
// An id by which the route is known to the media router service.
// Null if this route only exists as an artifact within this process.
String mGlobalRouteId;
// A predetermined connection status that can override mStatus
private int mStatusCode;
@ -1084,19 +1422,20 @@ public class MediaRouter {
/** @hide */ public static final int STATUS_AVAILABLE = 3;
/** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
/** @hide */ public static final int STATUS_IN_USE = 5;
/** @hide */ public static final int STATUS_CONNECTED = 6;
private Object mTag;
/**
* The default playback type, "local", indicating the presentation of the media is happening
* on the same device (e.g. a phone, a tablet) as where it is controlled from.
* @see #setPlaybackType(int)
* @see #getPlaybackType()
*/
public final static int PLAYBACK_TYPE_LOCAL = 0;
/**
* A playback type indicating the presentation of the media is happening on
* a different device (i.e. the remote device) than where it is controlled from.
* @see #setPlaybackType(int)
* @see #getPlaybackType()
*/
public final static int PLAYBACK_TYPE_REMOTE = 1;
/**
@ -1104,12 +1443,13 @@ public class MediaRouter {
* controlled from this object. An example of fixed playback volume is a remote player,
* playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
* than attenuate at the source.
* @see #setVolumeHandling(int)
* @see #getVolumeHandling()
*/
public final static int PLAYBACK_VOLUME_FIXED = 0;
/**
* Playback information indicating the playback volume is variable and can be controlled
* from this object.
* @see #getVolumeHandling()
*/
public final static int PLAYBACK_VOLUME_VARIABLE = 1;
@ -1181,7 +1521,7 @@ public class MediaRouter {
boolean setStatusCode(int statusCode) {
if (statusCode != mStatusCode) {
mStatusCode = statusCode;
int resId = 0;
int resId;
switch (statusCode) {
case STATUS_SCANNING:
resId = com.android.internal.R.string.media_route_status_scanning;
@ -1198,6 +1538,11 @@ public class MediaRouter {
case STATUS_IN_USE:
resId = com.android.internal.R.string.media_route_status_in_use;
break;
case STATUS_CONNECTED:
case STATUS_NONE:
default:
resId = 0;
break;
}
mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
return true;
@ -1317,9 +1662,7 @@ public class MediaRouter {
Log.e(TAG, "Error setting local stream volume", e);
}
} else {
Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
"Non-local volume playback on system route? " +
"Could not request volume change.");
sStatic.requestSetVolume(this, volume);
}
}
@ -1338,9 +1681,7 @@ public class MediaRouter {
Log.e(TAG, "Error setting local stream volume", e);
}
} else {
Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
"Non-local volume playback on system route? " +
"Could not request volume change.");
sStatic.requestUpdateVolume(this, direction);
}
}
@ -1418,7 +1759,19 @@ public class MediaRouter {
* @return True if this route is in the process of connecting.
*/
public boolean isConnecting() {
return mStatusCode == STATUS_CONNECTING;
// If the route is selected and its status appears to be between states
// then report it as connecting even though it has not yet had a chance
// to move into the CONNECTING state. Note that routes in the NONE state
// are assumed to not require an explicit connection lifecycle.
if (this == sStatic.mSelectedRoute) {
switch (mStatusCode) {
case STATUS_AVAILABLE:
case STATUS_SCANNING:
case STATUS_CONNECTING:
return true;
}
}
return false;
}
void setStatusInt(CharSequence status) {
@ -1432,6 +1785,7 @@ public class MediaRouter {
}
final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
@Override
public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
sStatic.mHandler.post(new Runnable() {
@Override
@ -1460,7 +1814,7 @@ public class MediaRouter {
", status=" + getStatus() +
", category=" + getCategory() +
", supportedTypes=" + supportedTypes +
", presentationDisplay=" + mPresentationDisplay + "}";
", presentationDisplay=" + mPresentationDisplay + " }";
}
}
@ -1716,6 +2070,7 @@ public class MediaRouter {
mVolumeHandling = PLAYBACK_VOLUME_FIXED;
}
@Override
CharSequence getName(Resources res) {
if (mUpdateName) updateName();
return super.getName(res);
@ -1916,7 +2271,7 @@ public class MediaRouter {
final int count = mRoutes.size();
if (count == 0) {
// Don't keep empty groups in the router.
MediaRouter.removeRoute(this);
MediaRouter.removeRouteStatic(this);
return;
}
@ -2071,6 +2426,7 @@ public class MediaRouter {
return mIsSystem;
}
@Override
public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " }";

View File

@ -0,0 +1,18 @@
/* Copyright 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 android.media;
parcelable MediaRouterClientState;

View File

@ -0,0 +1,183 @@
/*
* 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 android.media;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
/**
* Information available from MediaRouterService about the state perceived by
* a particular client and the routes that are available to it.
*
* Clients must not modify the contents of this object.
* @hide
*/
public final class MediaRouterClientState implements Parcelable {
/**
* A list of all known routes.
*/
public final ArrayList<RouteInfo> routes;
/**
* The id of the current globally selected route, or null if none.
* Globally selected routes override any other route selections that applications
* may have made. Used for remote displays.
*/
public String globallySelectedRouteId;
public MediaRouterClientState() {
routes = new ArrayList<RouteInfo>();
}
MediaRouterClientState(Parcel src) {
routes = src.createTypedArrayList(RouteInfo.CREATOR);
globallySelectedRouteId = src.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(routes);
dest.writeString(globallySelectedRouteId);
}
public static final Parcelable.Creator<MediaRouterClientState> CREATOR =
new Parcelable.Creator<MediaRouterClientState>() {
@Override
public MediaRouterClientState createFromParcel(Parcel in) {
return new MediaRouterClientState(in);
}
@Override
public MediaRouterClientState[] newArray(int size) {
return new MediaRouterClientState[size];
}
};
public static final class RouteInfo implements Parcelable {
public String id;
public String name;
public String description;
public int supportedTypes;
public boolean enabled;
public int statusCode;
public int playbackType;
public int playbackStream;
public int volume;
public int volumeMax;
public int volumeHandling;
public int presentationDisplayId;
public RouteInfo(String id) {
this.id = id;
enabled = true;
statusCode = MediaRouter.RouteInfo.STATUS_NONE;
playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
playbackStream = -1;
volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
presentationDisplayId = -1;
}
public RouteInfo(RouteInfo other) {
id = other.id;
name = other.name;
description = other.description;
supportedTypes = other.supportedTypes;
enabled = other.enabled;
statusCode = other.statusCode;
playbackType = other.playbackType;
playbackStream = other.playbackStream;
volume = other.volume;
volumeMax = other.volumeMax;
volumeHandling = other.volumeHandling;
presentationDisplayId = other.presentationDisplayId;
}
RouteInfo(Parcel in) {
id = in.readString();
name = in.readString();
description = in.readString();
supportedTypes = in.readInt();
enabled = in.readInt() != 0;
statusCode = in.readInt();
playbackType = in.readInt();
playbackStream = in.readInt();
volume = in.readInt();
volumeMax = in.readInt();
volumeHandling = in.readInt();
presentationDisplayId = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(name);
dest.writeString(description);
dest.writeInt(supportedTypes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(statusCode);
dest.writeInt(playbackType);
dest.writeInt(playbackStream);
dest.writeInt(volume);
dest.writeInt(volumeMax);
dest.writeInt(volumeHandling);
dest.writeInt(presentationDisplayId);
}
@Override
public String toString() {
return "RouteInfo{ id=" + id
+ ", name=" + name
+ ", description=" + description
+ ", supportedTypes=0x" + Integer.toHexString(supportedTypes)
+ ", enabled=" + enabled
+ ", statusCode=" + statusCode
+ ", playbackType=" + playbackType
+ ", playbackStream=" + playbackStream
+ ", volume=" + volume
+ ", volumeMax=" + volumeMax
+ ", volumeHandling=" + volumeHandling
+ ", presentationDisplayId=" + presentationDisplayId
+ " }";
}
@SuppressWarnings("hiding")
public static final Parcelable.Creator<RouteInfo> CREATOR =
new Parcelable.Creator<RouteInfo>() {
@Override
public RouteInfo createFromParcel(Parcel in) {
return new RouteInfo(in);
}
@Override
public RouteInfo[] newArray(int size) {
return new RouteInfo[size];
}
};
}
}

View File

@ -55,6 +55,7 @@ import com.android.server.content.ContentService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.media.MediaRouterService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.os.SchedulingPolicyService;
@ -356,6 +357,7 @@ class ServerThread {
DreamManagerService dreamy = null;
AssetAtlasService atlas = null;
PrintManagerService printManager = null;
MediaRouterService mediaRouter = null;
// Bring up services needed for UI.
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@ -804,6 +806,16 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("starting Print Service", e);
}
if (!disableNonCoreServices) {
try {
Slog.i(TAG, "Media Router Service");
mediaRouter = new MediaRouterService(context);
ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
} catch (Throwable e) {
reportWtf("starting MediaRouterService", e);
}
}
}
// Before things start rolling, be sure we have decided whether
@ -916,6 +928,7 @@ class ServerThread {
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final PrintManagerService printManagerF = printManager;
final MediaRouterService mediaRouterF = mediaRouter;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@ -1063,6 +1076,12 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("Notifying PrintManagerService running", e);
}
try {
if (mediaRouterF != null) mediaRouterF.systemRunning();
} catch (Throwable e) {
reportWtf("Notifying MediaRouterService running", e);
}
}
});

View File

@ -294,6 +294,7 @@ public class InputManagerService extends IInputManager.Stub
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
mContext.registerReceiver(new BroadcastReceiver() {
@Override

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,443 @@
/*
* 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.internal.util.Objects;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.IRemoteDisplayCallback;
import android.media.IRemoteDisplayProvider;
import android.media.RemoteDisplayState;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.IBinder.DeathRecipient;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
/**
* Maintains a connection to a particular remote display provider service.
*/
final class RemoteDisplayProviderProxy implements ServiceConnection {
private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final ComponentName mComponentName;
private final int mUserId;
private final Handler mHandler;
private Callback mDisplayStateCallback;
// Connection state
private boolean mRunning;
private boolean mBound;
private Connection mActiveConnection;
private boolean mConnectionReady;
// Logical state
private int mDiscoveryMode;
private String mSelectedDisplayId;
private RemoteDisplayState mDisplayState;
private boolean mScheduledDisplayStateChangedCallback;
public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
int userId) {
mContext = context;
mComponentName = componentName;
mUserId = userId;
mHandler = new Handler();
}
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "Proxy");
pw.println(prefix + " mUserId=" + mUserId);
pw.println(prefix + " mRunning=" + mRunning);
pw.println(prefix + " mBound=" + mBound);
pw.println(prefix + " mActiveConnection=" + mActiveConnection);
pw.println(prefix + " mConnectionReady=" + mConnectionReady);
pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode);
pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId);
pw.println(prefix + " mDisplayState=" + mDisplayState);
}
public void setCallback(Callback callback) {
mDisplayStateCallback = callback;
}
public RemoteDisplayState getDisplayState() {
return mDisplayState;
}
public void setDiscoveryMode(int mode) {
if (mDiscoveryMode != mode) {
mDiscoveryMode = mode;
if (mConnectionReady) {
mActiveConnection.setDiscoveryMode(mode);
}
updateBinding();
}
}
public void setSelectedDisplay(String id) {
if (!Objects.equal(mSelectedDisplayId, id)) {
if (mConnectionReady && mSelectedDisplayId != null) {
mActiveConnection.disconnect(mSelectedDisplayId);
}
mSelectedDisplayId = id;
if (mConnectionReady && id != null) {
mActiveConnection.connect(id);
}
updateBinding();
}
}
public void setDisplayVolume(int volume) {
if (mConnectionReady && mSelectedDisplayId != null) {
mActiveConnection.setVolume(mSelectedDisplayId, volume);
}
}
public void adjustDisplayVolume(int delta) {
if (mConnectionReady && mSelectedDisplayId != null) {
mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
}
}
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
}
public String getFlattenedComponentName() {
return mComponentName.flattenToShortString();
}
public void start() {
if (!mRunning) {
if (DEBUG) {
Slog.d(TAG, this + ": Starting");
}
mRunning = true;
updateBinding();
}
}
public void stop() {
if (mRunning) {
if (DEBUG) {
Slog.d(TAG, this + ": Stopping");
}
mRunning = false;
updateBinding();
}
}
public void rebindIfDisconnected() {
if (mActiveConnection == null && shouldBind()) {
unbind();
bind();
}
}
private void updateBinding() {
if (shouldBind()) {
bind();
} else {
unbind();
}
}
private boolean shouldBind() {
if (mRunning) {
// Bind whenever there is a discovery request or selected display.
if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
|| mSelectedDisplayId != null) {
return true;
}
}
return false;
}
private void bind() {
if (!mBound) {
if (DEBUG) {
Slog.d(TAG, this + ": Binding");
}
Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
service.setComponent(mComponentName);
try {
mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
new UserHandle(mUserId));
if (!mBound && DEBUG) {
Slog.d(TAG, this + ": Bind failed");
}
} catch (SecurityException ex) {
if (DEBUG) {
Slog.d(TAG, this + ": Bind failed", ex);
}
}
}
}
private void unbind() {
if (mBound) {
if (DEBUG) {
Slog.d(TAG, this + ": Unbinding");
}
mBound = false;
disconnect();
mContext.unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
Slog.d(TAG, this + ": Connected");
}
if (mBound) {
disconnect();
IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
if (provider != null) {
Connection connection = new Connection(provider);
if (connection.register()) {
mActiveConnection = connection;
} else {
if (DEBUG) {
Slog.d(TAG, this + ": Registration failed");
}
}
} else {
Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) {
Slog.d(TAG, this + ": Service disconnected");
}
disconnect();
}
private void onConnectionReady(Connection connection) {
if (mActiveConnection == connection) {
mConnectionReady = true;
if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
mActiveConnection.setDiscoveryMode(mDiscoveryMode);
}
if (mSelectedDisplayId != null) {
mActiveConnection.connect(mSelectedDisplayId);
}
}
}
private void onConnectionDied(Connection connection) {
if (mActiveConnection == connection) {
if (DEBUG) {
Slog.d(TAG, this + ": Service connection died");
}
disconnect();
}
}
private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
if (mActiveConnection == connection) {
if (DEBUG) {
Slog.d(TAG, this + ": State changed, state=" + state);
}
setDisplayState(state);
}
}
private void disconnect() {
if (mActiveConnection != null) {
if (mSelectedDisplayId != null) {
mActiveConnection.disconnect(mSelectedDisplayId);
}
mConnectionReady = false;
mActiveConnection.dispose();
mActiveConnection = null;
setDisplayState(null);
}
}
private void setDisplayState(RemoteDisplayState state) {
if (!Objects.equal(mDisplayState, state)) {
mDisplayState = state;
if (!mScheduledDisplayStateChangedCallback) {
mScheduledDisplayStateChangedCallback = true;
mHandler.post(mDisplayStateChanged);
}
}
}
@Override
public String toString() {
return "Service connection " + mComponentName.flattenToShortString();
}
private final Runnable mDisplayStateChanged = new Runnable() {
@Override
public void run() {
mScheduledDisplayStateChangedCallback = false;
if (mDisplayStateCallback != null) {
mDisplayStateCallback.onDisplayStateChanged(
RemoteDisplayProviderProxy.this, mDisplayState);
}
}
};
public interface Callback {
void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
}
private final class Connection implements DeathRecipient {
private final IRemoteDisplayProvider mProvider;
private final ProviderCallback mCallback;
public Connection(IRemoteDisplayProvider provider) {
mProvider = provider;
mCallback = new ProviderCallback(this);
}
public boolean register() {
try {
mProvider.asBinder().linkToDeath(this, 0);
mProvider.setCallback(mCallback);
mHandler.post(new Runnable() {
@Override
public void run() {
onConnectionReady(Connection.this);
}
});
return true;
} catch (RemoteException ex) {
binderDied();
}
return false;
}
public void dispose() {
mProvider.asBinder().unlinkToDeath(this, 0);
mCallback.dispose();
}
public void setDiscoveryMode(int mode) {
try {
mProvider.setDiscoveryMode(mode);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
}
}
public void connect(String id) {
try {
mProvider.connect(id);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
}
}
public void disconnect(String id) {
try {
mProvider.disconnect(id);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
}
}
public void setVolume(String id, int volume) {
try {
mProvider.setVolume(id, volume);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
}
}
public void adjustVolume(String id, int volume) {
try {
mProvider.adjustVolume(id, volume);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
}
}
@Override
public void binderDied() {
mHandler.post(new Runnable() {
@Override
public void run() {
onConnectionDied(Connection.this);
}
});
}
void postStateChanged(final RemoteDisplayState state) {
mHandler.post(new Runnable() {
@Override
public void run() {
onDisplayStateChanged(Connection.this, state);
}
});
}
}
/**
* Receives callbacks from the service.
* <p>
* This inner class is static and only retains a weak reference to the connection
* to prevent the client from being leaked in case the service is holding an
* active reference to the client's callback.
* </p>
*/
private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
private final WeakReference<Connection> mConnectionRef;
public ProviderCallback(Connection connection) {
mConnectionRef = new WeakReference<Connection>(connection);
}
public void dispose() {
mConnectionRef.clear();
}
@Override
public void onStateChanged(RemoteDisplayState state) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postStateChanged(state);
}
}
}
}

View File

@ -0,0 +1,181 @@
/*
* 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 android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.media.RemoteDisplayState;
import android.os.Handler;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
/**
* Watches for remote display provider services to be installed.
* Adds a provider to the media router for each registered service.
*
* @see RemoteDisplayProviderProxy
*/
public final class RemoteDisplayProviderWatcher {
private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final Callback mCallback;
private final Handler mHandler;
private final int mUserId;
private final PackageManager mPackageManager;
private final ArrayList<RemoteDisplayProviderProxy> mProviders =
new ArrayList<RemoteDisplayProviderProxy>();
private boolean mRunning;
public RemoteDisplayProviderWatcher(Context context,
Callback callback, Handler handler, int userId) {
mContext = context;
mCallback = callback;
mHandler = handler;
mUserId = userId;
mPackageManager = context.getPackageManager();
}
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "Watcher");
pw.println(prefix + " mUserId=" + mUserId);
pw.println(prefix + " mRunning=" + mRunning);
pw.println(prefix + " mProviders.size()=" + mProviders.size());
}
public void start() {
if (!mRunning) {
mRunning = true;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mScanPackagesReceiver,
new UserHandle(mUserId), filter, null, mHandler);
// Scan packages.
// Also has the side-effect of restarting providers if needed.
mHandler.post(mScanPackagesRunnable);
}
}
public void stop() {
if (mRunning) {
mRunning = false;
mContext.unregisterReceiver(mScanPackagesReceiver);
mHandler.removeCallbacks(mScanPackagesRunnable);
// Stop all providers.
for (int i = mProviders.size() - 1; i >= 0; i--) {
mProviders.get(i).stop();
}
}
}
private void scanPackages() {
if (!mRunning) {
return;
}
// Add providers for all new services.
// Reorder the list so that providers left at the end will be the ones to remove.
int targetIndex = 0;
Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
intent, 0, mUserId)) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo != null) {
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
RemoteDisplayProviderProxy provider =
new RemoteDisplayProviderProxy(mContext,
new ComponentName(serviceInfo.packageName, serviceInfo.name),
mUserId);
provider.start();
mProviders.add(targetIndex++, provider);
mCallback.addProvider(provider);
} else if (sourceIndex >= targetIndex) {
RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex);
provider.start(); // restart the provider if needed
provider.rebindIfDisconnected();
Collections.swap(mProviders, sourceIndex, targetIndex++);
}
}
}
// Remove providers for missing services.
if (targetIndex < mProviders.size()) {
for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
RemoteDisplayProviderProxy provider = mProviders.get(i);
mCallback.removeProvider(provider);
mProviders.remove(provider);
provider.stop();
}
}
}
private int findProvider(String packageName, String className) {
int count = mProviders.size();
for (int i = 0; i < count; i++) {
RemoteDisplayProviderProxy provider = mProviders.get(i);
if (provider.hasComponentName(packageName, className)) {
return i;
}
}
return -1;
}
private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Slog.d(TAG, "Received package manager broadcast: " + intent);
}
scanPackages();
}
};
private final Runnable mScanPackagesRunnable = new Runnable() {
@Override
public void run() {
scanPackages();
}
};
public interface Callback {
void addProvider(RemoteDisplayProviderProxy provider);
void removeProvider(RemoteDisplayProviderProxy provider);
}
}

View File

@ -0,0 +1,25 @@
# 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.
LOCAL_PATH := $(call my-dir)
# Build the application.
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := RemoteDisplayProviderTest
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay
include $(BUILD_PACKAGE)

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.media.remotedisplay.test" >
<uses-sdk android:minSdkVersion="19" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_app">
<uses-library android:name="com.android.media.remotedisplay"
android:required="true" />
<service android:name=".RemoteDisplayProviderService"
android:label="@string/app_name"
android:exported="true"
android:permission="android.permission.BIND_REMOTE_DISPLAY">
<intent-filter>
<action android:name="com.android.media.remotedisplay.RemoteDisplayProvider"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -0,0 +1,16 @@
This directory contains sample code to test system integration with
remote display providers using the API declared by the
com.android.media.remotedisplay.jar library.
--- DESCRIPTION ---
The application registers a service that publishes a few different
remote display routes. Behavior can be controlled by modifying the
code.
To exercise the provider, use System UI features for connecting to
wireless displays or launch an activity that uses the MediaRouter,
such as the PresentationWithMediaRouterActivity in ApiDemos.
This code is mainly intended for development and not meant to be
used as an example implementation of a robust remote display provider.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Remote Display Provider Test</string>
</resources>

View File

@ -0,0 +1,240 @@
/*
* 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.media.remotedisplay.test;
import com.android.media.remotedisplay.RemoteDisplay;
import com.android.media.remotedisplay.RemoteDisplayProvider;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
/**
* Remote display provider implementation that publishes working routes.
*/
public class RemoteDisplayProviderService extends Service {
private static final String TAG = "RemoteDisplayProviderTest";
private Provider mProvider;
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) {
if (mProvider == null) {
mProvider = new Provider();
return mProvider.getBinder();
}
}
return null;
}
final class Provider extends RemoteDisplayProvider {
private RemoteDisplay mTestDisplay1; // variable volume
private RemoteDisplay mTestDisplay2; // fixed volume
private RemoteDisplay mTestDisplay3; // not available
private RemoteDisplay mTestDisplay4; // in use
private RemoteDisplay mTestDisplay5; // available but ignores request to connect
private RemoteDisplay mTestDisplay6; // available but never finishes connecting
private RemoteDisplay mTestDisplay7; // blinks in and out of existence
private final Handler mHandler;
private boolean mBlinking;
public Provider() {
super(RemoteDisplayProviderService.this);
mHandler = new Handler(getMainLooper());
}
@Override
public void onDiscoveryModeChanged(int mode) {
Log.d(TAG, "onDiscoveryModeChanged: mode=" + mode);
if (mode != DISCOVERY_MODE_NONE) {
// When discovery begins, go find all of the routes.
if (mTestDisplay1 == null) {
mTestDisplay1 = new RemoteDisplay("testDisplay1",
"Test Display 1 (variable)");
mTestDisplay1.setDescription("Variable volume");
mTestDisplay1.setStatus(RemoteDisplay.STATUS_AVAILABLE);
mTestDisplay1.setVolume(10);
mTestDisplay1.setVolumeHandling(RemoteDisplay.PLAYBACK_VOLUME_VARIABLE);
mTestDisplay1.setVolumeMax(15);
addDisplay(mTestDisplay1);
}
if (mTestDisplay2 == null) {
mTestDisplay2 = new RemoteDisplay("testDisplay2",
"Test Display 2 (fixed)");
mTestDisplay2.setDescription("Fixed volume");
mTestDisplay2.setStatus(RemoteDisplay.STATUS_AVAILABLE);
addDisplay(mTestDisplay2);
}
if (mTestDisplay3 == null) {
mTestDisplay3 = new RemoteDisplay("testDisplay3",
"Test Display 3 (unavailable)");
mTestDisplay3.setDescription("Always unavailable");
mTestDisplay3.setStatus(RemoteDisplay.STATUS_NOT_AVAILABLE);
addDisplay(mTestDisplay3);
}
if (mTestDisplay4 == null) {
mTestDisplay4 = new RemoteDisplay("testDisplay4",
"Test Display 4 (in-use)");
mTestDisplay4.setDescription("Always in-use");
mTestDisplay4.setStatus(RemoteDisplay.STATUS_IN_USE);
addDisplay(mTestDisplay4);
}
if (mTestDisplay5 == null) {
mTestDisplay5 = new RemoteDisplay("testDisplay5",
"Test Display 5 (connect ignored)");
mTestDisplay5.setDescription("Ignores connect");
mTestDisplay5.setStatus(RemoteDisplay.STATUS_AVAILABLE);
addDisplay(mTestDisplay5);
}
if (mTestDisplay6 == null) {
mTestDisplay6 = new RemoteDisplay("testDisplay6",
"Test Display 6 (connect hangs)");
mTestDisplay6.setDescription("Never finishes connecting");
mTestDisplay6.setStatus(RemoteDisplay.STATUS_AVAILABLE);
addDisplay(mTestDisplay6);
}
} else {
// When discovery ends, go hide some of the routes we can't actually use.
// This isn't something a normal route provider would do though.
// The routes will usually stay published.
if (mTestDisplay3 != null) {
removeDisplay(mTestDisplay3);
mTestDisplay3 = null;
}
if (mTestDisplay4 != null) {
removeDisplay(mTestDisplay4);
mTestDisplay4 = null;
}
}
// When active discovery is on, pretend there's a route that we can't quite
// reach that blinks in and out of existence.
if (mode == DISCOVERY_MODE_ACTIVE) {
if (!mBlinking) {
mBlinking = true;
mHandler.post(mBlink);
}
} else {
mBlinking = false;
}
}
@Override
public void onConnect(final RemoteDisplay display) {
Log.d(TAG, "onConnect: display.getId()=" + display.getId());
if (display == mTestDisplay1 || display == mTestDisplay2) {
display.setStatus(RemoteDisplay.STATUS_CONNECTING);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if ((display == mTestDisplay1 || display == mTestDisplay2)
&& display.getStatus() == RemoteDisplay.STATUS_CONNECTING) {
display.setStatus(RemoteDisplay.STATUS_CONNECTED);
updateDisplay(display);
}
}
}, 2000);
updateDisplay(display);
}
if (display == mTestDisplay6 || display == mTestDisplay7) {
// never finishes connecting
display.setStatus(RemoteDisplay.STATUS_CONNECTING);
updateDisplay(display);
}
}
@Override
public void onDisconnect(RemoteDisplay display) {
Log.d(TAG, "onDisconnect: display.getId()=" + display.getId());
if (display == mTestDisplay1 || display == mTestDisplay2
|| display == mTestDisplay6) {
display.setStatus(RemoteDisplay.STATUS_AVAILABLE);
updateDisplay(display);
}
}
@Override
public void onSetVolume(RemoteDisplay display, int volume) {
Log.d(TAG, "onSetVolume: display.getId()=" + display.getId()
+ ", volume=" + volume);
if (display == mTestDisplay1) {
display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), volume)));
updateDisplay(display);
}
}
@Override
public void onAdjustVolume(RemoteDisplay display, int delta) {
Log.d(TAG, "onAdjustVolume: display.getId()=" + display.getId()
+ ", delta=" + delta);
if (display == mTestDisplay1) {
display.setVolume(Math.max(0, Math.min(display.getVolumeMax(),
display .getVolume() + delta)));
updateDisplay(display);
}
}
@Override
public void addDisplay(RemoteDisplay display) {
Log.d(TAG, "addDisplay: display=" + display);
super.addDisplay(display);
}
@Override
public void removeDisplay(RemoteDisplay display) {
Log.d(TAG, "removeDisplay: display=" + display);
super.removeDisplay(display);
}
@Override
public void updateDisplay(RemoteDisplay display) {
Log.d(TAG, "updateDisplay: display=" + display);
super.updateDisplay(display);
}
private final Runnable mBlink = new Runnable() {
@Override
public void run() {
if (mTestDisplay7 == null) {
if (mBlinking) {
mTestDisplay7 = new RemoteDisplay("testDisplay7",
"Test Display 7 (blinky)");
mTestDisplay7.setDescription("Comes and goes but can't connect");
mTestDisplay7.setStatus(RemoteDisplay.STATUS_AVAILABLE);
addDisplay(mTestDisplay7);
mHandler.postDelayed(this, 7000);
}
} else {
removeDisplay(mTestDisplay7);
mTestDisplay7 = null;
if (mBlinking) {
mHandler.postDelayed(this, 4000);
}
}
}
};
}
}