Merge "Add media router service and integrate with remote displays." into klp-dev
This commit is contained in:
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
24
media/java/android/media/IMediaRouterClient.aidl
Normal file
24
media/java/android/media/IMediaRouterClient.aidl
Normal 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();
|
||||
}
|
35
media/java/android/media/IMediaRouterService.aidl
Normal file
35
media/java/android/media/IMediaRouterService.aidl
Normal 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);
|
||||
}
|
@ -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 + " }";
|
||||
|
18
media/java/android/media/MediaRouterClientState.aidl
Normal file
18
media/java/android/media/MediaRouterClientState.aidl
Normal 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;
|
183
media/java/android/media/MediaRouterClientState.java
Normal file
183
media/java/android/media/MediaRouterClientState.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
1351
services/java/com/android/server/media/MediaRouterService.java
Normal file
1351
services/java/com/android/server/media/MediaRouterService.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
25
tests/RemoteDisplayProvider/Android.mk
Normal file
25
tests/RemoteDisplayProvider/Android.mk
Normal 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)
|
37
tests/RemoteDisplayProvider/AndroidManifest.xml
Normal file
37
tests/RemoteDisplayProvider/AndroidManifest.xml
Normal 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>
|
16
tests/RemoteDisplayProvider/README
Normal file
16
tests/RemoteDisplayProvider/README
Normal 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.
|
BIN
tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
Executable file
BIN
tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
Normal file
BIN
tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
19
tests/RemoteDisplayProvider/res/values/strings.xml
Normal file
19
tests/RemoteDisplayProvider/res/values/strings.xml
Normal 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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user