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/IAudioService.aidl \
|
||||||
media/java/android/media/IAudioFocusDispatcher.aidl \
|
media/java/android/media/IAudioFocusDispatcher.aidl \
|
||||||
media/java/android/media/IAudioRoutesObserver.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/IMediaScannerListener.aidl \
|
||||||
media/java/android/media/IMediaScannerService.aidl \
|
media/java/android/media/IMediaScannerService.aidl \
|
||||||
media/java/android/media/IRemoteControlClient.aidl \
|
media/java/android/media/IRemoteControlClient.aidl \
|
||||||
|
@ -60,7 +60,7 @@ public class MediaRouteActionProvider extends ActionProvider {
|
|||||||
}
|
}
|
||||||
mRouteTypes = types;
|
mRouteTypes = types;
|
||||||
if (types != 0) {
|
if (types != 0) {
|
||||||
mRouter.addCallback(types, mCallback);
|
mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
|
||||||
}
|
}
|
||||||
if (mView != null) {
|
if (mView != null) {
|
||||||
mView.setRouteTypes(mRouteTypes);
|
mView.setRouteTypes(mRouteTypes);
|
||||||
|
@ -123,14 +123,14 @@ public class MediaRouteButton extends View {
|
|||||||
|
|
||||||
if (mToggleMode) {
|
if (mToggleMode) {
|
||||||
if (mRemoteActive) {
|
if (mRemoteActive) {
|
||||||
mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute());
|
mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true);
|
||||||
} else {
|
} else {
|
||||||
final int N = mRouter.getRouteCount();
|
final int N = mRouter.getRouteCount();
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
final RouteInfo route = mRouter.getRouteAt(i);
|
final RouteInfo route = mRouter.getRouteAt(i);
|
||||||
if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
|
if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
|
||||||
route != mRouter.getDefaultRoute()) {
|
route != mRouter.getDefaultRoute()) {
|
||||||
mRouter.selectRouteInt(mRouteTypes, route);
|
mRouter.selectRouteInt(mRouteTypes, route, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +201,8 @@ public class MediaRouteButton extends View {
|
|||||||
|
|
||||||
if (mAttachedToWindow) {
|
if (mAttachedToWindow) {
|
||||||
updateRouteInfo();
|
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() {
|
void updateRemoteIndicator() {
|
||||||
final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
|
final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
|
||||||
final boolean isRemote = selected != mRouter.getDefaultRoute();
|
final boolean isRemote = selected != mRouter.getDefaultRoute();
|
||||||
final boolean isConnecting = selected != null &&
|
final boolean isConnecting = selected != null && selected.isConnecting();
|
||||||
selected.getStatusCode() == RouteInfo.STATUS_CONNECTING;
|
|
||||||
|
|
||||||
boolean needsRefresh = false;
|
boolean needsRefresh = false;
|
||||||
if (mRemoteActive != isRemote) {
|
if (mRemoteActive != isRemote) {
|
||||||
@ -238,7 +238,7 @@ public class MediaRouteButton extends View {
|
|||||||
void updateRouteCount() {
|
void updateRouteCount() {
|
||||||
final int N = mRouter.getRouteCount();
|
final int N = mRouter.getRouteCount();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
boolean hasVideoRoutes = false;
|
boolean scanRequired = false;
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
final RouteInfo route = mRouter.getRouteAt(i);
|
final RouteInfo route = mRouter.getRouteAt(i);
|
||||||
final int routeTypes = route.getSupportedTypes();
|
final int routeTypes = route.getSupportedTypes();
|
||||||
@ -248,8 +248,9 @@ public class MediaRouteButton extends View {
|
|||||||
} else {
|
} else {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
|
if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO
|
||||||
hasVideoRoutes = true;
|
| MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
|
||||||
|
scanRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,9 +258,10 @@ public class MediaRouteButton extends View {
|
|||||||
setEnabled(count != 0);
|
setEnabled(count != 0);
|
||||||
|
|
||||||
// Only allow toggling if we have more than just user routes.
|
// 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.
|
// Don't toggle if we support video or remote display routes, we may have to
|
||||||
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
|
// let the dialog scan.
|
||||||
!hasVideoRoutes;
|
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0
|
||||||
|
&& !scanRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -313,7 +315,8 @@ public class MediaRouteButton extends View {
|
|||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
mAttachedToWindow = true;
|
mAttachedToWindow = true;
|
||||||
if (mRouteTypes != 0) {
|
if (mRouteTypes != 0) {
|
||||||
mRouter.addCallback(mRouteTypes, mRouterCallback);
|
mRouter.addCallback(mRouteTypes, mRouterCallback,
|
||||||
|
MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
|
||||||
updateRouteInfo();
|
updateRouteInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,6 +643,15 @@ public final class Display {
|
|||||||
|| uid == 0;
|
|| 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() {
|
private void updateDisplayInfoLocked() {
|
||||||
// Note: The display manager caches display info objects on our behalf.
|
// Note: The display manager caches display info objects on our behalf.
|
||||||
DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
|
DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
|
||||||
|
@ -501,7 +501,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
final RouteInfo route = (RouteInfo) item;
|
final RouteInfo route = (RouteInfo) item;
|
||||||
if (type == VIEW_ROUTE) {
|
if (type == VIEW_ROUTE) {
|
||||||
mRouter.selectRouteInt(mRouteTypes, route);
|
mRouter.selectRouteInt(mRouteTypes, route, true);
|
||||||
dismiss();
|
dismiss();
|
||||||
} else if (type == VIEW_GROUPING_ROUTE) {
|
} else if (type == VIEW_GROUPING_ROUTE) {
|
||||||
final Checkable c = (Checkable) view;
|
final Checkable c = (Checkable) view;
|
||||||
@ -514,7 +514,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
|
|||||||
if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
|
if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
|
||||||
// Old group was selected but is now empty. Select the group
|
// Old group was selected but is now empty. Select the group
|
||||||
// we're manipulating since that's where the last route went.
|
// we're manipulating since that's where the last route went.
|
||||||
mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
|
mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
|
||||||
}
|
}
|
||||||
oldGroup.removeRoute(route);
|
oldGroup.removeRoute(route);
|
||||||
mEditingGroup.addRoute(route);
|
mEditingGroup.addRoute(route);
|
||||||
@ -555,7 +555,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
|
|||||||
mEditingGroup = group;
|
mEditingGroup = group;
|
||||||
mCategoryEditingGroups = group.getCategory();
|
mCategoryEditingGroups = group.getCategory();
|
||||||
getDialog().setCanceledOnTouchOutside(false);
|
getDialog().setCanceledOnTouchOutside(false);
|
||||||
mRouter.selectRouteInt(mRouteTypes, mEditingGroup);
|
mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true);
|
||||||
update();
|
update();
|
||||||
scrollToEditingGroup();
|
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;
|
package android.media;
|
||||||
|
|
||||||
|
import com.android.internal.util.Objects;
|
||||||
|
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -30,6 +32,7 @@ import android.os.Handler;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
@ -52,14 +55,17 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
*/
|
*/
|
||||||
public class MediaRouter {
|
public class MediaRouter {
|
||||||
private static final String TAG = "MediaRouter";
|
private static final String TAG = "MediaRouter";
|
||||||
|
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
static class Static implements DisplayManager.DisplayListener {
|
static class Static implements DisplayManager.DisplayListener {
|
||||||
// Time between wifi display scans when actively scanning in milliseconds.
|
// Time between wifi display scans when actively scanning in milliseconds.
|
||||||
private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
|
private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
|
||||||
|
|
||||||
|
final Context mAppContext;
|
||||||
final Resources mResources;
|
final Resources mResources;
|
||||||
final IAudioService mAudioService;
|
final IAudioService mAudioService;
|
||||||
final DisplayManager mDisplayService;
|
final DisplayManager mDisplayService;
|
||||||
|
final IMediaRouterService mMediaRouterService;
|
||||||
final Handler mHandler;
|
final Handler mHandler;
|
||||||
final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
|
final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
|
||||||
new CopyOnWriteArrayList<CallbackInfo>();
|
new CopyOnWriteArrayList<CallbackInfo>();
|
||||||
@ -79,6 +85,13 @@ public class MediaRouter {
|
|||||||
WifiDisplayStatus mLastKnownWifiDisplayStatus;
|
WifiDisplayStatus mLastKnownWifiDisplayStatus;
|
||||||
boolean mActivelyScanningWifiDisplays;
|
boolean mActivelyScanningWifiDisplays;
|
||||||
|
|
||||||
|
int mDiscoveryRequestRouteTypes;
|
||||||
|
boolean mDiscoverRequestActiveScan;
|
||||||
|
|
||||||
|
int mCurrentUserId = -1;
|
||||||
|
IMediaRouterClient mClient;
|
||||||
|
MediaRouterClientState mClientState;
|
||||||
|
|
||||||
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
|
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
|
||||||
@Override
|
@Override
|
||||||
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
|
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
|
||||||
@ -101,6 +114,7 @@ public class MediaRouter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Static(Context appContext) {
|
Static(Context appContext) {
|
||||||
|
mAppContext = appContext;
|
||||||
mResources = Resources.getSystem();
|
mResources = Resources.getSystem();
|
||||||
mHandler = new Handler(appContext.getMainLooper());
|
mHandler = new Handler(appContext.getMainLooper());
|
||||||
|
|
||||||
@ -109,6 +123,9 @@ public class MediaRouter {
|
|||||||
|
|
||||||
mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
|
mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
|
||||||
|
|
||||||
|
mMediaRouterService = IMediaRouterService.Stub.asInterface(
|
||||||
|
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
|
||||||
|
|
||||||
mSystemCategory = new RouteCategory(
|
mSystemCategory = new RouteCategory(
|
||||||
com.android.internal.R.string.default_audio_route_category_name,
|
com.android.internal.R.string.default_audio_route_category_name,
|
||||||
ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
|
ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
|
||||||
@ -146,10 +163,13 @@ public class MediaRouter {
|
|||||||
updateAudioRoutes(newAudioRoutes);
|
updateAudioRoutes(newAudioRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind to the media router service.
|
||||||
|
rebindAsUser(UserHandle.myUserId());
|
||||||
|
|
||||||
// Select the default route if the above didn't sync us up
|
// Select the default route if the above didn't sync us up
|
||||||
// appropriately with relevant system state.
|
// appropriately with relevant system state.
|
||||||
if (mSelectedRoute == null) {
|
if (mSelectedRoute == null) {
|
||||||
selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo);
|
selectDefaultRouteStatic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +217,7 @@ public class MediaRouter {
|
|||||||
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
|
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
|
||||||
}
|
}
|
||||||
} else if (sStatic.mBluetoothA2dpRoute != null) {
|
} else if (sStatic.mBluetoothA2dpRoute != null) {
|
||||||
removeRoute(sStatic.mBluetoothA2dpRoute);
|
removeRouteStatic(sStatic.mBluetoothA2dpRoute);
|
||||||
sStatic.mBluetoothA2dpRoute = null;
|
sStatic.mBluetoothA2dpRoute = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,16 +225,52 @@ public class MediaRouter {
|
|||||||
if (mBluetoothA2dpRoute != null) {
|
if (mBluetoothA2dpRoute != null) {
|
||||||
if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
|
if (mainType != AudioRoutesInfo.MAIN_SPEAKER &&
|
||||||
mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
|
mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) {
|
||||||
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
|
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
|
||||||
} else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
|
} else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) &&
|
||||||
a2dpEnabled) {
|
a2dpEnabled) {
|
||||||
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
|
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateActiveScan() {
|
void updateDiscoveryRequest() {
|
||||||
if (hasActiveScanCallbackOfType(ROUTE_TYPE_LIVE_VIDEO)) {
|
// 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) {
|
if (!mActivelyScanningWifiDisplays) {
|
||||||
mActivelyScanningWifiDisplays = true;
|
mActivelyScanningWifiDisplays = true;
|
||||||
mHandler.post(mScanWifiDisplays);
|
mHandler.post(mScanWifiDisplays);
|
||||||
@ -225,19 +281,15 @@ public class MediaRouter {
|
|||||||
mHandler.removeCallbacks(mScanWifiDisplays);
|
mHandler.removeCallbacks(mScanWifiDisplays);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasActiveScanCallbackOfType(int type) {
|
// Tell the media router service all about it.
|
||||||
final int count = mCallbacks.size();
|
if (routeTypes != mDiscoveryRequestRouteTypes
|
||||||
for (int i = 0; i < count; i++) {
|
|| activeScan != mDiscoverRequestActiveScan) {
|
||||||
CallbackInfo cbi = mCallbacks.get(i);
|
mDiscoveryRequestRouteTypes = routeTypes;
|
||||||
if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0
|
mDiscoverRequestActiveScan = activeScan;
|
||||||
&& (cbi.type & type) != 0) {
|
publishClientDiscoveryRequest();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayAdded(int displayId) {
|
public void onDisplayAdded(int displayId) {
|
||||||
@ -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;
|
static Static sStatic;
|
||||||
@ -285,7 +601,7 @@ public class MediaRouter {
|
|||||||
* <p>Once initiated this routing is transparent to the application. All audio
|
* <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>
|
* 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.
|
* Route type flag for live video.
|
||||||
@ -302,7 +618,13 @@ public class MediaRouter {
|
|||||||
* @see RouteInfo#getPresentationDisplay()
|
* @see RouteInfo#getPresentationDisplay()
|
||||||
* @see android.app.Presentation
|
* @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.
|
* 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
|
* is expected to interpret the meaning of these events and perform the requested
|
||||||
* routing tasks.</p>
|
* 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
|
* 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.
|
* Flag for {@link #addCallback}: Do not filter route events.
|
||||||
* <p>
|
* <p>
|
||||||
* When this flag is specified, the callback will be invoked for event that affect any
|
* 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>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
|
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
|
// Maps application contexts
|
||||||
static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
|
static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
|
||||||
|
|
||||||
@ -352,6 +693,9 @@ public class MediaRouter {
|
|||||||
if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
|
if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) {
|
||||||
result.append("ROUTE_TYPE_LIVE_VIDEO ");
|
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) {
|
if ((types & ROUTE_TYPE_USER) != 0) {
|
||||||
result.append("ROUTE_TYPE_USER ");
|
result.append("ROUTE_TYPE_USER ");
|
||||||
}
|
}
|
||||||
@ -453,9 +797,7 @@ public class MediaRouter {
|
|||||||
info = new CallbackInfo(cb, types, flags, this);
|
info = new CallbackInfo(cb, types, flags, this);
|
||||||
sStatic.mCallbacks.add(info);
|
sStatic.mCallbacks.add(info);
|
||||||
}
|
}
|
||||||
if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
|
sStatic.updateDiscoveryRequest();
|
||||||
sStatic.updateActiveScan();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,10 +808,8 @@ public class MediaRouter {
|
|||||||
public void removeCallback(Callback cb) {
|
public void removeCallback(Callback cb) {
|
||||||
int index = findCallbackInfo(cb);
|
int index = findCallbackInfo(cb);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
CallbackInfo info = sStatic.mCallbacks.remove(index);
|
sStatic.mCallbacks.remove(index);
|
||||||
if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) {
|
sStatic.updateDiscoveryRequest();
|
||||||
sStatic.updateActiveScan();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
|
Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
|
||||||
}
|
}
|
||||||
@ -499,17 +839,17 @@ public class MediaRouter {
|
|||||||
* @param route Route to select
|
* @param route Route to select
|
||||||
*/
|
*/
|
||||||
public void selectRoute(int types, RouteInfo route) {
|
public void selectRoute(int types, RouteInfo route) {
|
||||||
selectRouteStatic(types, route);
|
selectRouteStatic(types, route, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide internal use
|
* @hide internal use
|
||||||
*/
|
*/
|
||||||
public void selectRouteInt(int types, RouteInfo route) {
|
public void selectRouteInt(int types, RouteInfo route, boolean explicit) {
|
||||||
selectRouteStatic(types, route);
|
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;
|
final RouteInfo oldRoute = sStatic.mSelectedRoute;
|
||||||
if (oldRoute == route) return;
|
if (oldRoute == route) return;
|
||||||
if ((route.getSupportedTypes() & types) == 0) {
|
if ((route.getSupportedTypes() & types) == 0) {
|
||||||
@ -541,15 +881,26 @@ public class MediaRouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sStatic.setSelectedRoute(route, explicit);
|
||||||
|
|
||||||
if (oldRoute != null) {
|
if (oldRoute != null) {
|
||||||
dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
|
dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
|
||||||
}
|
}
|
||||||
sStatic.mSelectedRoute = route;
|
|
||||||
if (route != null) {
|
if (route != null) {
|
||||||
dispatchRouteSelected(types & route.getSupportedTypes(), route);
|
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.
|
* Compare the device address of a display and a route.
|
||||||
* Nulls/no device address will match another null/no address.
|
* Nulls/no device address will match another null/no address.
|
||||||
@ -612,7 +963,7 @@ public class MediaRouter {
|
|||||||
* @see #addUserRoute(UserRouteInfo)
|
* @see #addUserRoute(UserRouteInfo)
|
||||||
*/
|
*/
|
||||||
public void removeUserRoute(UserRouteInfo info) {
|
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.
|
// TODO Right now, RouteGroups only ever contain user routes.
|
||||||
// The code below will need to change if this assumption does.
|
// The code below will need to change if this assumption does.
|
||||||
if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
|
if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
|
||||||
removeRouteAt(i);
|
removeRouteStatic(info);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,10 +987,10 @@ public class MediaRouter {
|
|||||||
* @hide internal use only
|
* @hide internal use only
|
||||||
*/
|
*/
|
||||||
public void removeRouteInt(RouteInfo info) {
|
public void removeRouteInt(RouteInfo info) {
|
||||||
removeRoute(info);
|
removeRouteStatic(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void removeRoute(RouteInfo info) {
|
static void removeRouteStatic(RouteInfo info) {
|
||||||
if (sStatic.mRoutes.remove(info)) {
|
if (sStatic.mRoutes.remove(info)) {
|
||||||
final RouteCategory removingCat = info.getCategory();
|
final RouteCategory removingCat = info.getCategory();
|
||||||
final int count = sStatic.mRoutes.size();
|
final int count = sStatic.mRoutes.size();
|
||||||
@ -653,40 +1004,7 @@ public class MediaRouter {
|
|||||||
}
|
}
|
||||||
if (info == sStatic.mSelectedRoute) {
|
if (info == sStatic.mSelectedRoute) {
|
||||||
// Removing the currently selected route? Select the default before we remove it.
|
// 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.
|
selectDefaultRouteStatic();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
sStatic.mCategories.remove(removingCat);
|
sStatic.mCategories.remove(removingCat);
|
||||||
@ -752,7 +1070,7 @@ public class MediaRouter {
|
|||||||
*
|
*
|
||||||
* @see #addUserRoute(UserRouteInfo)
|
* @see #addUserRoute(UserRouteInfo)
|
||||||
* @see #removeUserRoute(UserRouteInfo)
|
* @see #removeUserRoute(UserRouteInfo)
|
||||||
* @see #createRouteCategory(CharSequence)
|
* @see #createRouteCategory(CharSequence, boolean)
|
||||||
*/
|
*/
|
||||||
public UserRouteInfo createUserRoute(RouteCategory category) {
|
public UserRouteInfo createUserRoute(RouteCategory category) {
|
||||||
return new UserRouteInfo(category);
|
return new UserRouteInfo(category);
|
||||||
@ -780,6 +1098,23 @@ public class MediaRouter {
|
|||||||
return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable);
|
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) {
|
static void updateRoute(final RouteInfo info) {
|
||||||
dispatchRouteChanged(info);
|
dispatchRouteChanged(info);
|
||||||
}
|
}
|
||||||
@ -906,7 +1241,7 @@ public class MediaRouter {
|
|||||||
updateWifiDisplayRoute(route, d, newStatus);
|
updateWifiDisplayRoute(route, d, newStatus);
|
||||||
}
|
}
|
||||||
if (d.equals(activeDisplay)) {
|
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,
|
// Don't scan if we're already connected to a wifi display,
|
||||||
// the scanning process can cause a hiccup with some configurations.
|
// the scanning process can cause a hiccup with some configurations.
|
||||||
@ -919,7 +1254,7 @@ public class MediaRouter {
|
|||||||
if (d.isRemembered()) {
|
if (d.isRemembered()) {
|
||||||
final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
|
final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
|
||||||
if (newDisplay == null || !newDisplay.isRemembered()) {
|
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) {
|
static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) {
|
||||||
int newStatus = RouteInfo.STATUS_NONE;
|
int newStatus;
|
||||||
|
|
||||||
if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
|
if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) {
|
||||||
newStatus = RouteInfo.STATUS_SCANNING;
|
newStatus = RouteInfo.STATUS_SCANNING;
|
||||||
} else if (d.isAvailable()) {
|
} else if (d.isAvailable()) {
|
||||||
@ -947,7 +1281,7 @@ public class MediaRouter {
|
|||||||
final int activeState = wfdStatus.getActiveDisplayState();
|
final int activeState = wfdStatus.getActiveDisplayState();
|
||||||
switch (activeState) {
|
switch (activeState) {
|
||||||
case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
|
case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
|
||||||
newStatus = RouteInfo.STATUS_NONE;
|
newStatus = RouteInfo.STATUS_CONNECTED;
|
||||||
break;
|
break;
|
||||||
case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
|
case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
|
||||||
newStatus = RouteInfo.STATUS_CONNECTING;
|
newStatus = RouteInfo.STATUS_CONNECTING;
|
||||||
@ -968,7 +1302,8 @@ public class MediaRouter {
|
|||||||
static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
|
static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) {
|
||||||
final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
|
final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
|
||||||
newRoute.mDeviceAddress = display.getDeviceAddress();
|
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.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
|
||||||
newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
|
newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
|
||||||
|
|
||||||
@ -1004,8 +1339,7 @@ public class MediaRouter {
|
|||||||
|
|
||||||
if (!enabled && route == sStatic.mSelectedRoute) {
|
if (!enabled && route == sStatic.mSelectedRoute) {
|
||||||
// Oops, no longer available. Reselect the default.
|
// Oops, no longer available. Reselect the default.
|
||||||
final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
|
selectDefaultRouteStatic();
|
||||||
selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1075,6 +1409,10 @@ public class MediaRouter {
|
|||||||
String mDeviceAddress;
|
String mDeviceAddress;
|
||||||
boolean mEnabled = true;
|
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
|
// A predetermined connection status that can override mStatus
|
||||||
private int mStatusCode;
|
private int mStatusCode;
|
||||||
|
|
||||||
@ -1084,19 +1422,20 @@ public class MediaRouter {
|
|||||||
/** @hide */ public static final int STATUS_AVAILABLE = 3;
|
/** @hide */ public static final int STATUS_AVAILABLE = 3;
|
||||||
/** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
|
/** @hide */ public static final int STATUS_NOT_AVAILABLE = 4;
|
||||||
/** @hide */ public static final int STATUS_IN_USE = 5;
|
/** @hide */ public static final int STATUS_IN_USE = 5;
|
||||||
|
/** @hide */ public static final int STATUS_CONNECTED = 6;
|
||||||
|
|
||||||
private Object mTag;
|
private Object mTag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default playback type, "local", indicating the presentation of the media is happening
|
* 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.
|
* 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;
|
public final static int PLAYBACK_TYPE_LOCAL = 0;
|
||||||
/**
|
/**
|
||||||
* A playback type indicating the presentation of the media is happening on
|
* 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.
|
* 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;
|
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,
|
* 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
|
* playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
|
||||||
* than attenuate at the source.
|
* than attenuate at the source.
|
||||||
* @see #setVolumeHandling(int)
|
* @see #getVolumeHandling()
|
||||||
*/
|
*/
|
||||||
public final static int PLAYBACK_VOLUME_FIXED = 0;
|
public final static int PLAYBACK_VOLUME_FIXED = 0;
|
||||||
/**
|
/**
|
||||||
* Playback information indicating the playback volume is variable and can be controlled
|
* Playback information indicating the playback volume is variable and can be controlled
|
||||||
* from this object.
|
* from this object.
|
||||||
|
* @see #getVolumeHandling()
|
||||||
*/
|
*/
|
||||||
public final static int PLAYBACK_VOLUME_VARIABLE = 1;
|
public final static int PLAYBACK_VOLUME_VARIABLE = 1;
|
||||||
|
|
||||||
@ -1181,7 +1521,7 @@ public class MediaRouter {
|
|||||||
boolean setStatusCode(int statusCode) {
|
boolean setStatusCode(int statusCode) {
|
||||||
if (statusCode != mStatusCode) {
|
if (statusCode != mStatusCode) {
|
||||||
mStatusCode = statusCode;
|
mStatusCode = statusCode;
|
||||||
int resId = 0;
|
int resId;
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case STATUS_SCANNING:
|
case STATUS_SCANNING:
|
||||||
resId = com.android.internal.R.string.media_route_status_scanning;
|
resId = com.android.internal.R.string.media_route_status_scanning;
|
||||||
@ -1198,6 +1538,11 @@ public class MediaRouter {
|
|||||||
case STATUS_IN_USE:
|
case STATUS_IN_USE:
|
||||||
resId = com.android.internal.R.string.media_route_status_in_use;
|
resId = com.android.internal.R.string.media_route_status_in_use;
|
||||||
break;
|
break;
|
||||||
|
case STATUS_CONNECTED:
|
||||||
|
case STATUS_NONE:
|
||||||
|
default:
|
||||||
|
resId = 0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
|
mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
|
||||||
return true;
|
return true;
|
||||||
@ -1317,9 +1662,7 @@ public class MediaRouter {
|
|||||||
Log.e(TAG, "Error setting local stream volume", e);
|
Log.e(TAG, "Error setting local stream volume", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
|
sStatic.requestSetVolume(this, volume);
|
||||||
"Non-local volume playback on system route? " +
|
|
||||||
"Could not request volume change.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1338,9 +1681,7 @@ public class MediaRouter {
|
|||||||
Log.e(TAG, "Error setting local stream volume", e);
|
Log.e(TAG, "Error setting local stream volume", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
|
sStatic.requestUpdateVolume(this, direction);
|
||||||
"Non-local volume playback on system route? " +
|
|
||||||
"Could not request volume change.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1418,7 +1759,19 @@ public class MediaRouter {
|
|||||||
* @return True if this route is in the process of connecting.
|
* @return True if this route is in the process of connecting.
|
||||||
*/
|
*/
|
||||||
public boolean isConnecting() {
|
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) {
|
void setStatusInt(CharSequence status) {
|
||||||
@ -1432,6 +1785,7 @@ public class MediaRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
|
final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() {
|
||||||
|
@Override
|
||||||
public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
|
public void dispatchRemoteVolumeUpdate(final int direction, final int value) {
|
||||||
sStatic.mHandler.post(new Runnable() {
|
sStatic.mHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -1716,6 +2070,7 @@ public class MediaRouter {
|
|||||||
mVolumeHandling = PLAYBACK_VOLUME_FIXED;
|
mVolumeHandling = PLAYBACK_VOLUME_FIXED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
CharSequence getName(Resources res) {
|
CharSequence getName(Resources res) {
|
||||||
if (mUpdateName) updateName();
|
if (mUpdateName) updateName();
|
||||||
return super.getName(res);
|
return super.getName(res);
|
||||||
@ -1916,7 +2271,7 @@ public class MediaRouter {
|
|||||||
final int count = mRoutes.size();
|
final int count = mRoutes.size();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
// Don't keep empty groups in the router.
|
// Don't keep empty groups in the router.
|
||||||
MediaRouter.removeRoute(this);
|
MediaRouter.removeRouteStatic(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2071,6 +2426,7 @@ public class MediaRouter {
|
|||||||
return mIsSystem;
|
return mIsSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
|
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
|
||||||
" groupable=" + mGroupable + " }";
|
" 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.display.DisplayManagerService;
|
||||||
import com.android.server.dreams.DreamManagerService;
|
import com.android.server.dreams.DreamManagerService;
|
||||||
import com.android.server.input.InputManagerService;
|
import com.android.server.input.InputManagerService;
|
||||||
|
import com.android.server.media.MediaRouterService;
|
||||||
import com.android.server.net.NetworkPolicyManagerService;
|
import com.android.server.net.NetworkPolicyManagerService;
|
||||||
import com.android.server.net.NetworkStatsService;
|
import com.android.server.net.NetworkStatsService;
|
||||||
import com.android.server.os.SchedulingPolicyService;
|
import com.android.server.os.SchedulingPolicyService;
|
||||||
@ -356,6 +357,7 @@ class ServerThread {
|
|||||||
DreamManagerService dreamy = null;
|
DreamManagerService dreamy = null;
|
||||||
AssetAtlasService atlas = null;
|
AssetAtlasService atlas = null;
|
||||||
PrintManagerService printManager = null;
|
PrintManagerService printManager = null;
|
||||||
|
MediaRouterService mediaRouter = null;
|
||||||
|
|
||||||
// Bring up services needed for UI.
|
// Bring up services needed for UI.
|
||||||
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
|
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
|
||||||
@ -804,6 +806,16 @@ class ServerThread {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
reportWtf("starting Print Service", 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
|
// Before things start rolling, be sure we have decided whether
|
||||||
@ -916,6 +928,7 @@ class ServerThread {
|
|||||||
final InputManagerService inputManagerF = inputManager;
|
final InputManagerService inputManagerF = inputManager;
|
||||||
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
|
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
|
||||||
final PrintManagerService printManagerF = printManager;
|
final PrintManagerService printManagerF = printManager;
|
||||||
|
final MediaRouterService mediaRouterF = mediaRouter;
|
||||||
|
|
||||||
// We now tell the activity manager it is okay to run third party
|
// 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
|
// code. It will call back into us once it has gotten to the state
|
||||||
@ -1063,6 +1076,12 @@ class ServerThread {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
reportWtf("Notifying PrintManagerService running", 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);
|
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||||
filter.addDataScheme("package");
|
filter.addDataScheme("package");
|
||||||
mContext.registerReceiver(new BroadcastReceiver() {
|
mContext.registerReceiver(new BroadcastReceiver() {
|
||||||
@Override
|
@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