summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk10
-rw-r--r--api/current.txt243
-rw-r--r--cmds/media/src/com/android/commands/media/Media.java6
-rw-r--r--core/java/android/provider/Settings.java15
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--media/java/android/media/routeprovider/IRouteProvider.aidl36
-rw-r--r--media/java/android/media/routeprovider/RouteConnection.java165
-rw-r--r--media/java/android/media/routeprovider/RouteInterfaceHandler.java246
-rw-r--r--media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java222
-rw-r--r--media/java/android/media/routeprovider/RouteProviderService.java228
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.java110
-rw-r--r--media/java/android/media/routing/IMediaRouteClientCallback.aidl41
-rw-r--r--media/java/android/media/routing/IMediaRouteService.aidl46
-rw-r--r--media/java/android/media/routing/IMediaRouter.aidl (renamed from media/java/android/media/routeprovider/IRouteConnection.aidl)16
-rw-r--r--media/java/android/media/routing/IMediaRouterDelegate.aidl (renamed from media/java/android/media/routeprovider/IRouteProviderCallback.aidl)20
-rw-r--r--media/java/android/media/routing/IMediaRouterRoutingCallback.aidl22
-rw-r--r--media/java/android/media/routing/IMediaRouterStateCallback.aidl22
-rw-r--r--media/java/android/media/routing/MediaRouteSelector.aidl (renamed from media/java/android/media/session/RouteInfo.aidl)4
-rw-r--r--media/java/android/media/routing/MediaRouteSelector.java357
-rw-r--r--media/java/android/media/routing/MediaRouteService.java1023
-rw-r--r--media/java/android/media/routing/MediaRouter.java1886
-rw-r--r--media/java/android/media/routing/ParcelableConnectionInfo.aidl (renamed from media/java/android/media/session/RouteEvent.aidl)4
-rw-r--r--media/java/android/media/routing/ParcelableConnectionInfo.java71
-rw-r--r--media/java/android/media/routing/ParcelableDestinationInfo.aidl (renamed from media/java/android/media/session/RouteCommand.aidl)4
-rw-r--r--media/java/android/media/routing/ParcelableDestinationInfo.java65
-rw-r--r--media/java/android/media/routing/ParcelableRouteInfo.aidl (renamed from media/java/android/media/routeprovider/RouteRequest.aidl)4
-rw-r--r--media/java/android/media/routing/ParcelableRouteInfo.java64
-rw-r--r--media/java/android/media/session/ISession.aidl15
-rw-r--r--media/java/android/media/session/ISessionCallback.aidl10
-rw-r--r--media/java/android/media/session/ISessionController.aidl9
-rw-r--r--media/java/android/media/session/ISessionControllerCallback.aidl4
-rw-r--r--media/java/android/media/session/MediaController.java60
-rw-r--r--media/java/android/media/session/MediaSession.java304
-rw-r--r--media/java/android/media/session/MediaSessionManager.java12
-rw-r--r--media/java/android/media/session/PlaybackState.java9
-rw-r--r--media/java/android/media/session/Route.java100
-rw-r--r--media/java/android/media/session/RouteCommand.java117
-rw-r--r--media/java/android/media/session/RouteEvent.java120
-rw-r--r--media/java/android/media/session/RouteInfo.java234
-rw-r--r--media/java/android/media/session/RouteInterface.java213
-rw-r--r--media/java/android/media/session/RouteOptions.aidl18
-rw-r--r--media/java/android/media/session/RouteOptions.java164
-rw-r--r--media/java/android/media/session/RoutePlaybackControls.java163
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderProxy.java419
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderWatcher.java239
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java309
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java182
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java6
-rw-r--r--services/core/java/com/android/server/media/RouteConnectionRecord.java118
-rw-r--r--tests/OneMedia/Android.mk3
-rw-r--r--tests/OneMedia/AndroidManifest.xml4
-rw-r--r--tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java2
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerController.java8
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerSession.java149
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java6
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java19
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java1
-rw-r--r--tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java186
59 files changed, 4214 insertions, 3933 deletions
diff --git a/Android.mk b/Android.mk
index 23c4753..96b2b17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -320,9 +320,12 @@ LOCAL_SRC_FILES += \
media/java/android/media/IRemoteVolumeObserver.aidl \
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
- media/java/android/media/routeprovider/IRouteConnection.aidl \
- media/java/android/media/routeprovider/IRouteProvider.aidl \
- media/java/android/media/routeprovider/IRouteProviderCallback.aidl \
+ media/java/android/media/routing/IMediaRouteService.aidl \
+ media/java/android/media/routing/IMediaRouteClientCallback.aidl \
+ media/java/android/media/routing/IMediaRouter.aidl \
+ media/java/android/media/routing/IMediaRouterDelegate.aidl \
+ media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
+ media/java/android/media/routing/IMediaRouterStateCallback.aidl \
media/java/android/media/session/IActiveSessionsListener.aidl \
media/java/android/media/session/ISessionController.aidl \
media/java/android/media/session/ISessionControllerCallback.aidl \
@@ -527,6 +530,7 @@ aidl_files := \
frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \
frameworks/base/media/java/android/media/MediaMetadata.aidl \
frameworks/base/media/java/android/media/Rating.aidl \
+ frameworks/base/media/java/android/media/routing/MediaRouteSelector.aidl \
frameworks/base/media/java/android/media/session/MediaSession.aidl \
frameworks/base/media/java/android/media/session/PlaybackState.aidl \
frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
diff --git a/api/current.txt b/api/current.txt
index d283360..623c281 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23,6 +23,7 @@ package android {
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+ field public static final java.lang.String BIND_MEDIA_ROUTE_SERVICE = "android.permission.BIND_MEDIA_ROUTE_SERVICE";
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
@@ -15871,12 +15872,249 @@ package android.media.effect {
}
+package android.media.routing {
+
+ public final class MediaRouteSelector implements android.os.Parcelable {
+ method public boolean containsProtocol(java.lang.Class<?>);
+ method public boolean containsProtocol(java.lang.String);
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public int getOptionalFeatures();
+ method public java.util.List<java.lang.String> getOptionalProtocols();
+ method public int getRequiredFeatures();
+ method public java.util.List<java.lang.String> getRequiredProtocols();
+ method public java.lang.String getServicePackageName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.Class<?>);
+ method public android.media.routing.MediaRouteSelector.Builder addOptionalProtocol(java.lang.String);
+ method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.Class<?>);
+ method public android.media.routing.MediaRouteSelector.Builder addRequiredProtocol(java.lang.String);
+ method public android.media.routing.MediaRouteSelector build();
+ method public android.media.routing.MediaRouteSelector.Builder setExtras(android.os.Bundle);
+ method public android.media.routing.MediaRouteSelector.Builder setOptionalFeatures(int);
+ method public android.media.routing.MediaRouteSelector.Builder setRequiredFeatures(int);
+ method public android.media.routing.MediaRouteSelector.Builder setServicePackageName(java.lang.String);
+ }
+
+ public abstract class MediaRouteService extends android.app.Service {
+ ctor public MediaRouteService();
+ method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract android.media.routing.MediaRouteService.ClientSession onCreateClientSession(android.media.routing.MediaRouteService.ClientInfo);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.media.routing.MediaRouteService";
+ }
+
+ public static final class MediaRouteService.ClientInfo {
+ method public java.lang.String getPackageName();
+ method public int getUid();
+ }
+
+ public static abstract class MediaRouteService.ClientSession {
+ ctor public MediaRouteService.ClientSession();
+ method public abstract boolean onConnect(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouteService.ConnectionCallback);
+ method public abstract void onDisconnect();
+ method public void onPauseStream();
+ method public void onRelease();
+ method public void onResumeStream();
+ method public abstract boolean onStartDiscovery(android.media.routing.MediaRouter.DiscoveryRequest, android.media.routing.MediaRouteService.DiscoveryCallback);
+ method public abstract void onStopDiscovery();
+ }
+
+ public final class MediaRouteService.ConnectionCallback {
+ method public void onConnected(android.media.routing.MediaRouter.ConnectionInfo);
+ method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+ method public void onDisconnected();
+ }
+
+ public final class MediaRouteService.DiscoveryCallback {
+ method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+ method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+ method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+ }
+
+ public final class MediaRouter {
+ ctor public MediaRouter(android.content.Context);
+ method public void addSelector(android.media.routing.MediaRouteSelector);
+ method public void clearSelectors();
+ method public android.media.routing.MediaRouter.Delegate createDelegate();
+ method public android.media.routing.MediaRouter.ConnectionInfo getConnection();
+ method public int getConnectionState();
+ method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+ method public java.util.List<android.media.routing.MediaRouter.RouteInfo> getDiscoveredRoutes(android.media.routing.MediaRouter.DestinationInfo);
+ method public int getDiscoveryState();
+ method public android.media.AudioAttributes getPreferredAudioAttributes();
+ method public android.view.Display getPreferredPresentationDisplay();
+ method public android.media.VolumeProvider getPreferredVolumeProvider();
+ method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+ method public android.media.routing.MediaRouter.RouteInfo getSelectedRoute();
+ method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+ method public boolean isReleased();
+ method public void pauseStream();
+ method public void release();
+ method public void removeSelector(android.media.routing.MediaRouteSelector);
+ method public void resumeStream();
+ method public void setRoutingCallback(android.media.routing.MediaRouter.RoutingCallback, android.os.Handler);
+ field public static final int CONNECTION_ERROR_ABORTED = 1; // 0x1
+ field public static final int CONNECTION_ERROR_BARGED = 7; // 0x7
+ field public static final int CONNECTION_ERROR_BROKEN = 6; // 0x6
+ field public static final int CONNECTION_ERROR_BUSY = 4; // 0x4
+ field public static final int CONNECTION_ERROR_TIMEOUT = 5; // 0x5
+ field public static final int CONNECTION_ERROR_UNAUTHORIZED = 2; // 0x2
+ field public static final int CONNECTION_ERROR_UNKNOWN = 0; // 0x0
+ field public static final int CONNECTION_ERROR_UNREACHABLE = 3; // 0x3
+ field public static final int CONNECTION_FLAG_BARGE = 1; // 0x1
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0; // 0x0
+ field public static final int DISCONNECTION_REASON_ERROR = 2; // 0x2
+ field public static final int DISCONNECTION_REASON_USER_REQUEST = 1; // 0x1
+ field public static final int DISCOVERY_ERROR_ABORTED = 1; // 0x1
+ field public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2; // 0x2
+ field public static final int DISCOVERY_ERROR_UNKNOWN = 0; // 0x0
+ field public static final int DISCOVERY_FLAG_BACKGROUND = 1; // 0x1
+ field public static final int DISCOVERY_STATE_STARTED = 1; // 0x1
+ field public static final int DISCOVERY_STATE_STOPPED = 0; // 0x0
+ field public static final int ROUTE_FEATURE_LIVE_AUDIO = 1; // 0x1
+ field public static final int ROUTE_FEATURE_LIVE_VIDEO = 2; // 0x2
+ }
+
+ public static final class MediaRouter.ConnectionInfo {
+ method public android.media.AudioAttributes getAudioAttributes();
+ method public android.os.Bundle getExtras();
+ method public int getFeatures();
+ method public android.view.Display getPresentationDisplay();
+ method public android.os.IBinder getProtocolBinder(java.lang.String);
+ method public android.os.IBinder getProtocolBinder(int);
+ method public T getProtocolObject(java.lang.Class<T>);
+ method public java.util.List<java.lang.String> getProtocols();
+ method public android.media.routing.MediaRouter.RouteInfo getRoute();
+ method public android.media.VolumeProvider getVolumeProvider();
+ }
+
+ public static final class MediaRouter.ConnectionInfo.Builder {
+ ctor public MediaRouter.ConnectionInfo.Builder(android.media.routing.MediaRouter.RouteInfo);
+ method public android.media.routing.MediaRouter.ConnectionInfo build();
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setAudioAttributes(android.media.AudioAttributes);
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setExtras(android.os.Bundle);
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setPresentationDisplay(android.view.Display);
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolBinder(java.lang.String, android.os.IBinder);
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setProtocolStub(java.lang.Class<?>, android.os.IInterface);
+ method public android.media.routing.MediaRouter.ConnectionInfo.Builder setVolumeProvider(android.media.VolumeProvider);
+ }
+
+ public static final class MediaRouter.ConnectionRequest {
+ method public android.os.Bundle getExtras();
+ method public int getFlags();
+ method public android.media.routing.MediaRouter.RouteInfo getRoute();
+ method public void setExtras(android.os.Bundle);
+ method public void setFlags(int);
+ method public void setRoute(android.media.routing.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.Delegate {
+ ctor public MediaRouter.Delegate();
+ method public void addStateCallback(android.media.routing.MediaRouter.StateCallback, android.os.Handler);
+ method public void connect(android.media.routing.MediaRouter.DestinationInfo, int);
+ method public void disconnect(int);
+ method public int getConnectionState();
+ method public java.util.List<android.media.routing.MediaRouter.DestinationInfo> getDiscoveredDestinations();
+ method public int getDiscoveryState();
+ method public android.media.routing.MediaRouter.DestinationInfo getSelectedDestination();
+ method public boolean isReleased();
+ method public void removeStateCallback(android.media.routing.MediaRouter.StateCallback);
+ method public void startDiscovery(int);
+ method public void stopDiscovery();
+ }
+
+ public static final class MediaRouter.DestinationInfo {
+ method public java.lang.CharSequence getDescription();
+ method public android.os.Bundle getExtras();
+ method public int getIconResourceId();
+ method public java.lang.String getId();
+ method public java.lang.CharSequence getName();
+ method public android.media.routing.MediaRouter.ServiceMetadata getServiceMetadata();
+ method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
+ }
+
+ public static final class MediaRouter.DestinationInfo.Builder {
+ ctor public MediaRouter.DestinationInfo.Builder(java.lang.String, android.media.routing.MediaRouter.ServiceMetadata, java.lang.CharSequence);
+ method public android.media.routing.MediaRouter.DestinationInfo build();
+ method public android.media.routing.MediaRouter.DestinationInfo.Builder setDescription(java.lang.CharSequence);
+ method public android.media.routing.MediaRouter.DestinationInfo.Builder setExtras(android.os.Bundle);
+ method public android.media.routing.MediaRouter.DestinationInfo.Builder setIconResourceId(int);
+ }
+
+ public static final class MediaRouter.DiscoveryRequest {
+ method public int getFlags();
+ method public java.util.List<android.media.routing.MediaRouteSelector> getSelectors();
+ method public void setFlags(int);
+ method public void setSelectors(java.util.List<android.media.routing.MediaRouteSelector>);
+ }
+
+ public static final class MediaRouter.RouteInfo {
+ method public android.media.routing.MediaRouter.DestinationInfo getDestination();
+ method public android.os.Bundle getExtras();
+ method public int getFeatures();
+ method public java.lang.String getId();
+ method public java.util.List<java.lang.String> getProtocols();
+ method public android.media.routing.MediaRouteSelector getSelector();
+ }
+
+ public static final class MediaRouter.RouteInfo.Builder {
+ ctor public MediaRouter.RouteInfo.Builder(java.lang.String, android.media.routing.MediaRouter.DestinationInfo, android.media.routing.MediaRouteSelector);
+ method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.Class<T>);
+ method public android.media.routing.MediaRouter.RouteInfo.Builder addProtocol(java.lang.String);
+ method public android.media.routing.MediaRouter.RouteInfo build();
+ method public android.media.routing.MediaRouter.RouteInfo.Builder setExtras(android.os.Bundle);
+ method public android.media.routing.MediaRouter.RouteInfo.Builder setFeatures(int);
+ }
+
+ public static abstract class MediaRouter.RoutingCallback extends android.media.routing.MediaRouter.StateCallback {
+ ctor public MediaRouter.RoutingCallback();
+ method public boolean onPrepareConnectionRequest(android.media.routing.MediaRouter.ConnectionRequest, android.media.routing.MediaRouter.DestinationInfo, java.util.List<android.media.routing.MediaRouter.RouteInfo>);
+ method public boolean onPrepareDiscoveryRequest(android.media.routing.MediaRouter.DiscoveryRequest, java.util.List<android.media.routing.MediaRouteSelector>);
+ }
+
+ public static final class MediaRouter.ServiceMetadata {
+ method public android.content.ComponentName getComponentName();
+ method public android.graphics.drawable.Drawable getIcon(android.content.pm.PackageManager);
+ method public java.lang.CharSequence getLabel(android.content.pm.PackageManager);
+ method public java.lang.String getPackageName();
+ method public android.content.pm.ServiceInfo getService();
+ }
+
+ public static abstract class MediaRouter.StateCallback {
+ ctor public MediaRouter.StateCallback();
+ method public void onConnected();
+ method public void onConnecting();
+ method public void onConnectionFailed(int, java.lang.CharSequence, android.os.Bundle);
+ method public void onConnectionStateChanged(int);
+ method public void onDestinationFound(android.media.routing.MediaRouter.DestinationInfo);
+ method public void onDestinationLost(android.media.routing.MediaRouter.DestinationInfo);
+ method public void onDisconnected();
+ method public void onDiscoveryFailed(int, java.lang.CharSequence, android.os.Bundle);
+ method public void onDiscoveryStarted();
+ method public void onDiscoveryStateChanged(int);
+ method public void onDiscoveryStopped();
+ method public void onReleased();
+ method public void onSelectedDestinationChanged(android.media.routing.MediaRouter.DestinationInfo);
+ }
+
+}
+
package android.media.session {
public final class MediaController {
method public void addCallback(android.media.session.MediaController.Callback);
method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
method public void adjustVolumeBy(int, int);
+ method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate();
method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
method public static android.media.session.MediaController fromToken(android.media.session.MediaSession.Token);
method public android.media.MediaMetadata getMetadata();
@@ -15931,6 +16169,7 @@ package android.media.session {
method public void setActive(boolean);
method public void setFlags(int);
method public void setLaunchPendingIntent(android.app.PendingIntent);
+ method public void setMediaRouter(android.media.routing.MediaRouter);
method public void setMetadata(android.media.MediaMetadata);
method public void setPlaybackState(android.media.session.PlaybackState);
method public void setPlaybackToLocal(int);
@@ -16005,6 +16244,7 @@ package android.media.session {
field public static final android.os.Parcelable.Creator CREATOR;
field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
field public static final int STATE_ERROR = 7; // 0x7
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
@@ -16268,8 +16508,8 @@ package android.media.tv {
method public void onChannelRetuned(java.lang.String, android.net.Uri);
method public void onError(java.lang.String, int);
method public void onTrackInfoChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>);
- method public void onVideoSizeChanged(java.lang.String, int, int);
method public void onVideoAvailable(java.lang.String);
+ method public void onVideoSizeChanged(java.lang.String, int, int);
method public void onVideoUnavailable(java.lang.String, int);
}
@@ -24659,6 +24899,7 @@ package android.provider {
field public static final java.lang.String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+ field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 4e91361..800b925 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -25,7 +25,6 @@ import android.media.session.ISessionManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionInfo;
import android.media.session.PlaybackState;
-import android.media.session.RouteInfo;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -181,11 +180,6 @@ public class Media extends BaseCommand {
}
@Override
- public void onRouteChanged(RouteInfo route) {
- System.out.println("onRouteChanged " + route);
- }
-
- @Override
public void onPlaybackStateChanged(PlaybackState state) {
System.out.println("onPlaybackStateChanged " + state);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 07397973..2687bcc 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -266,6 +266,21 @@ public final class Settings {
"android.settings.WIFI_DISPLAY_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of
+ * {@link android.media.routing.MediaRouteService media route providers}.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CAST_SETTINGS =
+ "android.settings.CAST_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of date and time.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4725cfb..350660b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2149,7 +2149,7 @@
android:description="@string/permdesc_bindTvInput"
android:protectionLevel="signature|system" />
- <!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
+ <!-- Must be required by a {@link android.media.routing.MediaRouteService}
to ensure that only the system can interact with it.
@hide -->
<permission android:name="android.permission.BIND_ROUTE_PROVIDER"
@@ -2720,6 +2720,13 @@
android:description="@string/permdesc_bindConditionProviderService"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link android.media.routing.MediaRouteService},
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE"
+ android:label="@string/permlab_bindMediaRouteService"
+ android:description="@string/permdesc_bindMediaRouteService"
+ android:protectionLevel="signature" />
+
<!-- Must be required by an {@link android.service.dreams.DreamService},
to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_DREAM_SERVICE"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5d57262..193d3c0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2124,6 +2124,11 @@
<string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindMediaRouteService">bind to a media route service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindMediaRouteService">Allows the holder to bind to the top-level interface of a media route service. Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindDreamService">bind to a dream service</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindDreamService">Allows the holder to bind to the top-level interface of a dream service. Should never be needed for normal apps.</string>
diff --git a/media/java/android/media/routeprovider/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
deleted file mode 100644
index c36f6a7..0000000
--- a/media/java/android/media/routeprovider/IRouteProvider.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Copyright (C) 2014 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.routeprovider;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteInfo;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-/**
- * Interface to an app's RouteProviderService.
- * @hide
- */
-oneway interface IRouteProvider {
- void registerCallback(in IRouteProviderCallback cb);
- void unregisterCallback(in IRouteProviderCallback cb);
- void updateDiscoveryRequests(in List<RouteRequest> requests);
-
- void getAvailableRoutes(in List<RouteRequest> requests, in ResultReceiver cb);
- void connect(in RouteInfo route, in RouteRequest request, in ResultReceiver cb);
-} \ No newline at end of file
diff --git a/media/java/android/media/routeprovider/RouteConnection.java b/media/java/android/media/routeprovider/RouteConnection.java
deleted file mode 100644
index 43692c1..0000000
--- a/media/java/android/media/routeprovider/RouteConnection.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2014 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.routeprovider;
-
-import android.media.routeprovider.IRouteConnection;
-import android.media.session.RouteCommand;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteInterface;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents an ongoing connection between an application and a media route
- * offered by a media route provider.
- * <p>
- * The media route provider should add interfaces to the connection before
- * returning it to the system in order to receive commands from clients on those
- * interfaces. Use {@link #addRouteInterface(String)} to add an interface and
- * {@link #getRouteInterface(String)} to retrieve the interface's handle anytime
- * after it has been added.
- * @hide
- */
-public final class RouteConnection {
- private static final String TAG = "RouteConnection";
- private final ConnectionStub mBinder;
- private final ArrayList<String> mIfaceNames = new ArrayList<String>();
- private final ArrayMap<String, RouteInterfaceHandler> mIfaces
- = new ArrayMap<String, RouteInterfaceHandler>();
- private final RouteProviderService mProvider;
- private final RouteInfo mRoute;
-
- private boolean mPublished;
-
- /**
- * Create a new connection for the given Provider and Route.
- *
- * @param provider The provider this route is associated with.
- * @param route The route this is a connection to.
- */
- public RouteConnection(RouteProviderService provider, RouteInfo route) {
- if (provider == null) {
- throw new IllegalArgumentException("provider may not be null.");
- }
- if (route == null) {
- throw new IllegalArgumentException("route may not be null.");
- }
- mBinder = new ConnectionStub(this);
- mProvider = provider;
- mRoute = route;
- }
-
- /**
- * Add an interface to this route connection. All interfaces must be added
- * to the connection before the connection is returned to the system.
- *
- * @param ifaceName The name of the interface to add
- * @return The route interface that was registered
- */
- public RouteInterfaceHandler addRouteInterface(String ifaceName) {
- if (TextUtils.isEmpty(ifaceName)) {
- throw new IllegalArgumentException("The interface's name may not be empty");
- }
- if (mPublished) {
- throw new IllegalStateException(
- "Connection has already been published to the system.");
- }
- RouteInterfaceHandler iface = mIfaces.get(ifaceName);
- if (iface == null) {
- iface = new RouteInterfaceHandler(this, ifaceName);
- mIfaceNames.add(ifaceName);
- mIfaces.put(ifaceName, iface);
- } else {
- Log.w(TAG, "Attempted to add an interface that already exists");
- }
- return iface;
- }
-
- /**
- * Get the interface instance for the specified interface name. If the
- * interface was not added to this connection null will be returned.
- *
- * @param ifaceName The name of the interface to get.
- * @return The route interface with that name or null.
- */
- public RouteInterfaceHandler getRouteInterface(String ifaceName) {
- return mIfaces.get(ifaceName);
- }
-
- /**
- * Close the connection and inform the system that it may no longer be used.
- */
- public void shutDown() {
- mProvider.disconnect(this);
- }
-
- /**
- * @hide
- */
- public void sendEvent(String iface, String event, Bundle extras) {
- RouteEvent e = new RouteEvent(mBinder, iface, event, extras);
- mProvider.sendRouteEvent(e);
- }
-
- /**
- * @hide
- */
- IRouteConnection.Stub getBinder() {
- return mBinder;
- }
-
- /**
- * @hide
- */
- void publish() {
- mPublished = true;
- }
-
- private static class ConnectionStub extends IRouteConnection.Stub {
- private final WeakReference<RouteConnection> mConnection;
-
- public ConnectionStub(RouteConnection connection) {
- mConnection = new WeakReference<RouteConnection>(connection);
- }
-
- @Override
- public void onCommand(RouteCommand command, ResultReceiver cb) {
- RouteConnection connection = mConnection.get();
- if (connection != null) {
- RouteInterfaceHandler iface = connection.mIfaces.get(command.getIface());
- if (iface != null) {
- iface.onCommand(command.getEvent(), command.getExtras(), cb);
- } else if (cb != null) {
- cb.send(RouteInterface.RESULT_INTERFACE_NOT_SUPPORTED, null);
- }
- }
- }
-
- @Override
- public void disconnect() {
- // TODO
- }
- }
-}
diff --git a/media/java/android/media/routeprovider/RouteInterfaceHandler.java b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
deleted file mode 100644
index e7f8bbf..0000000
--- a/media/java/android/media/routeprovider/RouteInterfaceHandler.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2014 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.routeprovider;
-
-import android.media.session.Route;
-import android.media.session.MediaSession;
-import android.media.session.RouteInterface;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Represents an interface that an application may use to send requests to a
- * connected media route.
- * <p>
- * A {@link RouteProviderService} may expose multiple interfaces on a
- * {@link RouteConnection} for a {@link MediaSession} to interact with. A
- * provider creates an interface with
- * {@link RouteConnection#addRouteInterface(String)} to allow messages to be
- * routed appropriately. Events are then sent through a specific interface and
- * all commands being sent on the interface will be sent to any registered
- * {@link CommandListener}s.
- * <p>
- * An interface instance can only be registered on one {@link RouteConnection}.
- * To use the same interface on multiple connections a new instance must be
- * created for each connection.
- * <p>
- * It is recommended you wrap this interface with a standard implementation to
- * avoid errors, but for simple interfaces this class may be used directly. TODO
- * add link to sample code.
- * @hide
- */
-public final class RouteInterfaceHandler {
- private static final String TAG = "RouteInterfaceHandler";
-
- private final Object mLock = new Object();
- private final RouteConnection mConnection;
- private final String mName;
-
- private ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
-
- /**
- * Create a new RouteInterface for a given connection. This can be used to
- * send events on the given interface and register listeners for commands
- * from the connected session.
- *
- * @param connection The connection this interface sends events on
- * @param ifaceName The name of this interface
- * @hide
- */
- public RouteInterfaceHandler(RouteConnection connection, String ifaceName) {
- if (connection == null) {
- throw new IllegalArgumentException("connection may not be null");
- }
- if (TextUtils.isEmpty(ifaceName)) {
- throw new IllegalArgumentException("ifaceName can not be empty");
- }
- mConnection = connection;
- mName = ifaceName;
- }
-
- /**
- * Send an event on this interface to the connected session.
- *
- * @param event The event to send
- * @param extras Any extras for the event
- */
- public void sendEvent(String event, Bundle extras) {
- mConnection.sendEvent(mName, event, extras);
- }
-
- /**
- * Send a result from a command to the specified callback. The result codes
- * in {@link RouteInterface} must be used. More information
- * about the result, whether successful or an error, should be included in
- * the extras.
- *
- * @param cb The callback to send the result to
- * @param resultCode The result code for the call
- * @param extras Any extras to include
- */
- public static void sendResult(ResultReceiver cb, int resultCode, Bundle extras) {
- if (cb != null) {
- cb.send(resultCode, extras);
- }
- }
-
- /**
- * Add a listener for this interface. If a handler is specified callbacks
- * will be performed on the handler's thread, otherwise the callers thread
- * will be used.
- *
- * @param listener The listener to receive calls on.
- * @param handler The handler whose thread to post calls on or null.
- */
- public void addListener(CommandListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
- synchronized (mLock) {
- if (findIndexOfListenerLocked(listener) != -1) {
- Log.d(TAG, "Listener is already added, ignoring");
- return;
- }
- mListeners.add(new MessageHandler(looper, listener));
- }
- }
-
- /**
- * Remove a listener from this interface.
- *
- * @param listener The listener to stop receiving commands on.
- */
- public void removeListener(CommandListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- synchronized (mLock) {
- int index = findIndexOfListenerLocked(listener);
- if (index != -1) {
- mListeners.remove(index);
- }
- }
- }
-
- /**
- * @hide
- */
- public void onCommand(String command, Bundle args, ResultReceiver cb) {
- synchronized (mLock) {
- Command cmd = new Command(command, args, cb);
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).post(MessageHandler.MSG_COMMAND, cmd);
- }
- }
- }
-
- /**
- * Get the interface name.
- *
- * @return The name of this interface
- */
- public String getName() {
- return mName;
- }
-
- private int findIndexOfListenerLocked(CommandListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- MessageHandler handler = mListeners.get(i);
- if (listener == handler.mListener) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Handles commands sent to the interface.
- * <p>
- * Register an InterfaceListener using {@link #addListener}.
- */
- public abstract static class CommandListener {
- /**
- * This is called when a command is received that matches this
- * interface. Commands are sent by a {@link MediaSession} that is
- * connected to the route this interface is registered with.
- *
- * @param iface The interface the command was received on.
- * @param command The command or method to invoke.
- * @param args Any args that were included with the command. May be
- * null.
- * @param cb The callback provided to send a response on. May be null.
- * @return true if the command was handled, false otherwise. If the
- * command was not handled an error will be sent automatically.
- * true may be returned if the command will be handled
- * asynchronously.
- * @see Route
- * @see MediaSession
- */
- public abstract boolean onCommand(RouteInterfaceHandler iface, String command, Bundle args,
- ResultReceiver cb);
- }
-
- private class MessageHandler extends Handler {
- private static final int MSG_COMMAND = 1;
-
- private final CommandListener mListener;
-
- public MessageHandler(Looper looper, CommandListener listener) {
- super(looper, null, true /* async */);
- mListener = listener;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_COMMAND:
- Command cmd = (Command) msg.obj;
- if (!mListener.onCommand(RouteInterfaceHandler.this, cmd.command, cmd.args, cmd.cb)) {
- sendResult(cmd.cb, RouteInterface.RESULT_COMMAND_NOT_SUPPORTED,
- null);
- }
- break;
- }
- }
-
- public void post(int what, Object obj) {
- obtainMessage(what, obj).sendToTarget();
- }
- }
-
- private final static class Command {
- public final String command;
- public final Bundle args;
- public final ResultReceiver cb;
-
- public Command(String command, Bundle args, ResultReceiver cb) {
- this.command = command;
- this.args = args;
- this.cb = cb;
- }
- }
-}
diff --git a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
deleted file mode 100644
index f2c40d2..0000000
--- a/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2014 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.routeprovider;
-
-import android.media.session.RoutePlaybackControls;
-import android.media.session.RouteInterface;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Standard wrapper for using playback controls over a {@link RouteInterfaceHandler}.
- * This is the provider half of the interface. Sessions should use
- * {@link RoutePlaybackControls} to interact with this interface.
- * @hide
- */
-public final class RoutePlaybackControlsHandler {
- private static final String TAG = "RoutePlaybackControls";
-
- private final RouteInterfaceHandler mIface;
-
- private RoutePlaybackControlsHandler(RouteInterfaceHandler iface) {
- mIface = iface;
- }
-
- /**
- * Add this interface to the specified route and return a handle for
- * communicating on the interface.
- *
- * @param connection The connection to register this interface on.
- * @return A handle for communicating on this interface.
- */
- public static RoutePlaybackControlsHandler addTo(RouteConnection connection) {
- if (connection == null) {
- throw new IllegalArgumentException("connection may not be null");
- }
- RouteInterfaceHandler iface = connection
- .addRouteInterface(RoutePlaybackControls.NAME);
-
- return new RoutePlaybackControlsHandler(iface);
- }
-
- /**
- * Add a {@link Listener} to this interface. The listener will receive
- * commands on the caller's thread.
- *
- * @param listener The listener to send commands to.
- */
- public void addListener(Listener listener) {
- addListener(listener, null);
- }
-
- /**
- * Add a {@link Listener} to this interface. The listener will receive
- * updates on the handler's thread. If no handler is specified the caller's
- * thread will be used instead.
- *
- * @param listener The listener to send commands to.
- * @param handler The handler whose thread calls should be posted on. May be
- * null.
- */
- public void addListener(Listener listener, Handler handler) {
- mIface.addListener(listener, handler);
- }
-
- /**
- * Remove a {@link Listener} from this interface.
- *
- * @param listener The Listener to remove.
- */
- public void removeListener(Listener listener) {
- mIface.removeListener(listener);
- }
-
- /**
- * Publish the current playback state to the system and any controllers.
- * Valid values are defined in {@link PlaybackState}. TODO create
- * RoutePlaybackState.
- *
- * @param state
- */
- public void sendPlaybackChangeEvent(int state) {
- Bundle extras = new Bundle();
- extras.putInt(RoutePlaybackControls.KEY_VALUE1, state);
- mIface.sendEvent(RoutePlaybackControls.EVENT_PLAYSTATE_CHANGE, extras);
- }
-
- /**
- * Command handler for the RoutePlaybackControls interface. You can add a
- * Listener to the interface using {@link #addListener}.
- */
- public static abstract class Listener extends RouteInterfaceHandler.CommandListener {
-
- @Override
- public final boolean onCommand(RouteInterfaceHandler iface, String method, Bundle extras,
- ResultReceiver cb) {
- if (RoutePlaybackControls.CMD_FAST_FORWARD.equals(method)) {
- boolean success = fastForward();
- // TODO specify type of error
- RouteInterfaceHandler.sendResult(cb, success
- ? RouteInterface.RESULT_SUCCESS
- : RouteInterface.RESULT_ERROR, null);
- return true;
- } else if (RoutePlaybackControls.CMD_GET_CURRENT_POSITION.equals(method)) {
- Bundle result = new Bundle();
- result.putLong(RoutePlaybackControls.KEY_VALUE1, getCurrentPosition());
- RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
- result);
- return true;
- } else if (RoutePlaybackControls.CMD_GET_CAPABILITIES.equals(method)) {
- Bundle result = new Bundle();
- result.putLong(RoutePlaybackControls.KEY_VALUE1, getCapabilities());
- RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS,
- result);
- return true;
- } else if (RoutePlaybackControls.CMD_PLAY_NOW.equals(method)) {
- playNow(extras.getString(RoutePlaybackControls.KEY_VALUE1, null), cb);
- return true;
- } else if (RoutePlaybackControls.CMD_RESUME.equals(method)) {
- boolean success = resume();
- RouteInterfaceHandler.sendResult(cb, success
- ? RouteInterface.RESULT_SUCCESS
- : RouteInterface.RESULT_ERROR, null);
- return true;
- } else if (RoutePlaybackControls.CMD_PAUSE.equals(method)) {
- boolean success = pause();
- RouteInterfaceHandler.sendResult(cb, success
- ? RouteInterface.RESULT_SUCCESS
- : RouteInterface.RESULT_ERROR, null);
- return true;
- } else {
- // The command wasn't recognized
- }
- return false;
- }
-
- /**
- * Override to handle fast forwarding.
- *
- * @return true if the request succeeded, false otherwise
- */
- public boolean fastForward() {
- Log.w(TAG, "fastForward is not supported.");
- return false;
- }
-
- /**
- * Override to handle getting the current position of playback in
- * millis.
- *
- * @return The current position in millis or -1
- */
- public long getCurrentPosition() {
- Log.w(TAG, "getCurrentPosition is not supported");
- return -1;
- }
-
- /**
- * Override to handle getting the set of capabilities currently
- * available.
- *
- * @return A bit mask of the supported capabilities
- */
- public long getCapabilities() {
- Log.w(TAG, "getCapabilities is not supported");
- return 0;
- }
-
- /**
- * Override to handle play now requests.
- *
- * @param content The uri of the item to play.
- * @param cb The callback to send the result to.
- */
- public void playNow(String content, ResultReceiver cb) {
- Log.w(TAG, "playNow is not supported");
- if (cb != null) {
- // We do this directly since we don't have a reference to the
- // iface
- cb.send(RouteInterface.RESULT_COMMAND_NOT_SUPPORTED, null);
- }
- }
-
- /**
- * Override to handle resume requests. Return true if the call was
- * handled, even if it was a no-op.
- *
- * @return true if the call was handled.
- */
- public boolean resume() {
- Log.w(TAG, "resume is not supported");
- return false;
- }
-
- /**
- * Override to handle pause requests. Return true if the call was
- * handled, even if it was a no-op.
- *
- * @return true if the call was handled.
- */
- public boolean pause() {
- Log.w(TAG, "pause is not supported");
- return false;
- }
- }
-}
diff --git a/media/java/android/media/routeprovider/RouteProviderService.java b/media/java/android/media/routeprovider/RouteProviderService.java
deleted file mode 100644
index a6ef0bb..0000000
--- a/media/java/android/media/routeprovider/RouteProviderService.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2014 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.routeprovider;
-
-import android.app.Service;
-import android.content.Intent;
-import android.media.routeprovider.IRouteProvider;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Base class for defining a route provider service.
- * <p>
- * A route provider offers media routes which represent destinations to which
- * applications may connect, control, and send content. This provides a means
- * for Android applications to interact with a variety of media streaming
- * devices such as speakers or television sets.
- * <p>
- * The system will bind to your provider when an active app is interested in
- * routes that may be discovered through your provider. After binding, the
- * system will send updates on which routes to discover through
- * {@link #updateDiscoveryRequests(List)}. The system will call
- * {@link #getMatchingRoutes(List)} with a subset of filters when a route is
- * needed for a specific app.
- * <p>
- * TODO add documentation for how the sytem knows an app is interested. Maybe
- * interface declarations in the manifest.
- * <p>
- * The system will only start a provider when an app may discover routes through
- * it. If your service needs to run at other times you are responsible for
- * managing its lifecycle.
- * <p>
- * Declare your route provider service in your application manifest like this:
- * <p>
- *
- * <pre>
- * &lt;service android:name=".MyRouteProviderService"
- * android:label="@string/my_route_provider_service">
- * &lt;intent-filter>
- * &lt;action android:name="com.android.media.session.MediaRouteProvider" />
- * &lt;/intent-filter>
- * &lt;/service>
- * </pre>
- * @hide
- */
-public abstract class RouteProviderService extends Service {
- private static final String TAG = "RouteProvider";
- /**
- * A service that implements a RouteProvider must declare that it handles
- * this action in its AndroidManifest.
- */
- public static final String SERVICE_INTERFACE =
- "com.android.media.session.MediaRouteProvider";
-
- /**
- * @hide
- */
- public static final String KEY_ROUTES = "routes";
- /**
- * @hide
- */
- public static final String KEY_CONNECTION = "connection";
- /**
- * @hide
- */
- public static final int RESULT_FAILURE = -1;
- /**
- * @hide
- */
- public static final int RESULT_SUCCESS = 0;
-
- // The system's callback once it has bound to the service
- private IRouteProviderCallback mCb;
-
- /**
- * If your service overrides onBind it must return super.onBind() in
- * response to the {@link #SERVICE_INTERFACE} action.
- */
- @Override
- public IBinder onBind(Intent intent) {
- if (intent != null && RouteProviderService.SERVICE_INTERFACE.equals(intent.getAction())) {
- return mBinder;
- }
- return null;
- }
-
- /**
- * Disconnect the specified RouteConnection. The system will stop sending
- * commands to this connection.
- *
- * @param connection The connection to disconnect.
- * @hide
- */
- public final void disconnect(RouteConnection connection) {
- if (mCb != null) {
- try {
- mCb.onConnectionTerminated(connection.getBinder());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error in disconnect.", e);
- }
- }
- }
-
- /**
- * @hide
- */
- public final void sendRouteEvent(RouteEvent event) {
- if (mCb != null) {
- try {
- mCb.onRouteEvent(event);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Unable to send MediaRouteEvent to system", e);
- }
- }
- }
-
- /**
- * Override to handle updates to the routes that are of interest. Each
- * {@link RouteRequest} will specify if it is an active or passive request.
- * Route discovery may perform more aggressive discovery on behalf of active
- * requests but should use low power discovery methods otherwise.
- * <p>
- * A single app may have more than one request. Your provider is responsible
- * for deciding the set of features that are important for discovery given
- * the set of requests. If your provider only has one method of discovery it
- * may simply verify that one or more requests are valid before starting
- * discovery.
- *
- * @param requests The route requests that are currently relevant.
- */
- public void updateDiscoveryRequests(List<RouteRequest> requests) {
- }
-
- /**
- * Return a list of matching routes for the given set of requests. Returning
- * null or an empty list indicates there are no matches. A route is
- * considered matching if it supports one or more of the
- * {@link RouteOptions} specified. Each returned {@link RouteInfo}
- * should include all the requested connections that it supports.
- *
- * @param options The set of requests for routes
- * @return The routes that this caller may connect to using one or more of
- * the route options.
- */
- public abstract List<RouteInfo> getMatchingRoutes(List<RouteRequest> options);
-
- /**
- * Handle a request to connect to a specific route with a specific request.
- * The {@link RouteConnection} must be fully defined before being returned,
- * though the actual connection to the route may be performed in the
- * background.
- *
- * @param route The route to connect to
- * @param request The connection request parameters
- * @return A MediaRouteConnection representing the connection to the route
- */
- public abstract RouteConnection connect(RouteInfo route, RouteRequest request);
-
- private IRouteProvider.Stub mBinder = new IRouteProvider.Stub() {
-
- @Override
- public void registerCallback(IRouteProviderCallback cb) throws RemoteException {
- mCb = cb;
- }
-
- @Override
- public void unregisterCallback(IRouteProviderCallback cb) throws RemoteException {
- mCb = null;
- }
-
- @Override
- public void updateDiscoveryRequests(List<RouteRequest> requests)
- throws RemoteException {
- RouteProviderService.this.updateDiscoveryRequests(requests);
- }
-
- @Override
- public void getAvailableRoutes(List<RouteRequest> requests, ResultReceiver cb)
- throws RemoteException {
- List<RouteInfo> routes = RouteProviderService.this.getMatchingRoutes(requests);
- ArrayList<RouteInfo> routesArray;
- if (routes instanceof ArrayList) {
- routesArray = (ArrayList<RouteInfo>) routes;
- } else {
- routesArray = new ArrayList<RouteInfo>(routes);
- }
- Bundle resultData = new Bundle();
- resultData.putParcelableArrayList(KEY_ROUTES, routesArray);
- cb.send(routes == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
- }
-
- @Override
- public void connect(RouteInfo route, RouteRequest request, ResultReceiver cb)
- throws RemoteException {
- RouteConnection connection = RouteProviderService.this.connect(route, request);
- Bundle resultData = new Bundle();
- if (connection != null) {
- connection.publish();
- resultData.putBinder(KEY_CONNECTION, connection.getBinder());
- }
-
- cb.send(connection == null ? RESULT_FAILURE : RESULT_SUCCESS, resultData);
- }
- };
-}
diff --git a/media/java/android/media/routeprovider/RouteRequest.java b/media/java/android/media/routeprovider/RouteRequest.java
deleted file mode 100644
index 2ba75de..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2014 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.routeprovider;
-
-import android.media.session.RouteOptions;
-import android.media.session.MediaSessionInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.io.PrintWriter;
-
-/**
- * A request to connect or discover routes with certain capabilities.
- * <p>
- * Passed to a {@link RouteProviderService} when a request for discovery or to
- * connect to a route is made. This identifies the app making the request and
- * provides the full set of connection parameters they would like to use for a
- * connection. An app that can connect in multiple ways will be represented by
- * multiple requests.
- * @hide
- */
-public final class RouteRequest implements Parcelable {
- private final MediaSessionInfo mSessionInfo;
- private final RouteOptions mOptions;
- private final boolean mActive;
-
- /**
- * @hide
- */
- public RouteRequest(MediaSessionInfo info, RouteOptions connRequest,
- boolean active) {
- mSessionInfo = info;
- mOptions = connRequest;
- mActive = active;
- }
-
- private RouteRequest(Parcel in) {
- mSessionInfo = MediaSessionInfo.CREATOR.createFromParcel(in);
- mOptions = RouteOptions.CREATOR.createFromParcel(in);
- mActive = in.readInt() != 0;
- }
-
- /**
- * Get information about the session making the request.
- *
- * @return Info on the session making the request
- */
- public MediaSessionInfo getSessionInfo() {
- return mSessionInfo;
- }
-
- /**
- * Get the connection options, which includes the interfaces and other
- * connection params the session wants to use with a route.
- *
- * @return The connection options
- */
- public RouteOptions getConnectionOptions() {
- return mOptions;
- }
-
- @Override
- public String toString() {
- StringBuilder bob = new StringBuilder();
- bob.append("RouteRequest {");
- bob.append("active=").append(mActive);
- bob.append(", info=").append(mSessionInfo.toString());
- bob.append(", options=").append(mOptions.toString());
- bob.append("}");
- return bob.toString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mSessionInfo.writeToParcel(dest, flags);
- mOptions.writeToParcel(dest, flags);
- dest.writeInt(mActive ? 1 : 0);
- }
-
- public static final Parcelable.Creator<RouteRequest> CREATOR
- = new Parcelable.Creator<RouteRequest>() {
- @Override
- public RouteRequest createFromParcel(Parcel in) {
- return new RouteRequest(in);
- }
-
- @Override
- public RouteRequest[] newArray(int size) {
- return new RouteRequest[size];
- }
- };
-}
diff --git a/media/java/android/media/routing/IMediaRouteClientCallback.aidl b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
new file mode 100644
index 0000000..d90ea3b
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteClientCallback.aidl
@@ -0,0 +1,41 @@
+/* Copyright (C) 2014 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.routing;
+
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.ParcelableConnectionInfo;
+import android.media.routing.ParcelableDestinationInfo;
+import android.media.routing.ParcelableRouteInfo;
+import android.os.IBinder;
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+oneway interface IMediaRouteClientCallback {
+ void onDestinationFound(int seq, in ParcelableDestinationInfo destination,
+ in ParcelableRouteInfo[] routes);
+
+ void onDestinationLost(int seq, String id);
+
+ void onDiscoveryFailed(int seq, int error, in CharSequence message, in Bundle extras);
+
+ void onConnected(int seq, in ParcelableConnectionInfo connection);
+
+ void onDisconnected(int seq);
+
+ void onConnectionFailed(int seq, int error, in CharSequence message, in Bundle extras);
+}
diff --git a/media/java/android/media/routing/IMediaRouteService.aidl b/media/java/android/media/routing/IMediaRouteService.aidl
new file mode 100644
index 0000000..493ab6d
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouteService.aidl
@@ -0,0 +1,46 @@
+/* Copyright (C) 2014 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.routing;
+
+import android.media.routing.IMediaRouteClientCallback;
+import android.media.routing.MediaRouteSelector;
+import android.os.Bundle;
+
+/**
+ * Interface to an app's MediaRouteService.
+ * @hide
+ */
+oneway interface IMediaRouteService {
+ void registerClient(int clientUid, String clientPackageName,
+ in IMediaRouteClientCallback callback);
+
+ void unregisterClient(in IMediaRouteClientCallback callback);
+
+ void startDiscovery(in IMediaRouteClientCallback callback, int seq,
+ in List<MediaRouteSelector> selectors, int flags);
+
+ void stopDiscovery(in IMediaRouteClientCallback callback);
+
+ void connect(in IMediaRouteClientCallback callback, int seq,
+ String destinationId, String routeId, int flags, in Bundle extras);
+
+ void disconnect(in IMediaRouteClientCallback callback);
+
+ void pauseStream(in IMediaRouteClientCallback callback);
+
+ void resumeStream(in IMediaRouteClientCallback callback);
+}
+
diff --git a/media/java/android/media/routeprovider/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouter.aidl
index 15c8039..0abb258 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouter.aidl
@@ -13,16 +13,10 @@
* limitations under the License.
*/
-package android.media.routeprovider;
+package android.media.routing;
-import android.media.session.RouteCommand;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouter {
+
+}
-/**
- * Interface for a specific connected route.
- * @hide
- */
-oneway interface IRouteConnection {
- void onCommand(in RouteCommand command, in ResultReceiver cb);
- void disconnect();
-} \ No newline at end of file
diff --git a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl b/media/java/android/media/routing/IMediaRouterDelegate.aidl
index 9185347..35f84c8 100644
--- a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
+++ b/media/java/android/media/routing/IMediaRouterDelegate.aidl
@@ -13,20 +13,10 @@
* limitations under the License.
*/
-package android.media.routeprovider;
+package android.media.routing;
-import android.media.routeprovider.IRouteConnection;
-import android.media.session.RouteEvent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
+/** @hide */
+interface IMediaRouterDelegate {
+
+}
-/**
- * System's provider callback interface.
- * @hide
- */
-oneway interface IRouteProviderCallback {
- void onRoutesChanged();
- void onConnectionStateChanged(in IRouteConnection connection, int state);
- void onConnectionTerminated(in IRouteConnection connection);
- void onRouteEvent(in RouteEvent event);
-} \ No newline at end of file
diff --git a/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
new file mode 100644
index 0000000..173ae55
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
@@ -0,0 +1,22 @@
+/* Copyright (C) 2014 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.routing;
+
+/** @hide */
+interface IMediaRouterRoutingCallback {
+
+}
+
diff --git a/media/java/android/media/routing/IMediaRouterStateCallback.aidl b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
new file mode 100644
index 0000000..0299904
--- /dev/null
+++ b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
@@ -0,0 +1,22 @@
+/* Copyright (C) 2014 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.routing;
+
+/** @hide */
+interface IMediaRouterStateCallback {
+
+}
+
diff --git a/media/java/android/media/session/RouteInfo.aidl b/media/java/android/media/routing/MediaRouteSelector.aidl
index c5f50c8..37bfa4a 100644
--- a/media/java/android/media/session/RouteInfo.aidl
+++ b/media/java/android/media/routing/MediaRouteSelector.aidl
@@ -13,6 +13,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.media.routing;
-parcelable RouteInfo;
+parcelable MediaRouteSelector;
diff --git a/media/java/android/media/routing/MediaRouteSelector.java b/media/java/android/media/routing/MediaRouteSelector.java
new file mode 100644
index 0000000..26a9b1c
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteSelector.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.routing.MediaRouter.RouteFeatures;
+import android.os.Bundle;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A media route selector consists of a set of constraints that are used to select
+ * the routes to which an application would like to connect. The constraints consist
+ * of a set of required or optional features and protocols. The constraints may also
+ * require the use of a specific media route service package or additional characteristics
+ * that are described by a bundle of extra parameters.
+ * <p>
+ * The application will typically create several different selectors that express
+ * various combinations of characteristics that it would like to use together when
+ * it connects to a destination media device. For each destination that is discovered,
+ * media route services will publish some number of routes and include information
+ * about which selector each route matches. The application will then choose among
+ * these routes to determine which best satisfies its desired purpose and connect to it.
+ * </p>
+ */
+public final class MediaRouteSelector implements Parcelable {
+ private final int mRequiredFeatures;
+ private final int mOptionalFeatures;
+ private final List<String> mRequiredProtocols;
+ private final List<String> mOptionalProtocols;
+ private final String mServicePackageName;
+ private final Bundle mExtras;
+
+ MediaRouteSelector(int requiredFeatures, int optionalFeatures,
+ List<String> requiredProtocols, List<String> optionalProtocols,
+ String servicePackageName, Bundle extras) {
+ mRequiredFeatures = requiredFeatures;
+ mOptionalFeatures = optionalFeatures;
+ mRequiredProtocols = requiredProtocols;
+ mOptionalProtocols = optionalProtocols;
+ mServicePackageName = servicePackageName;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the set of required route features.
+ *
+ * @return A set of required route feature flags.
+ */
+ public @RouteFeatures int getRequiredFeatures() {
+ return mRequiredFeatures;
+ }
+
+ /**
+ * Gets the set of optional route features.
+ *
+ * @return A set of optional route feature flags.
+ */
+ public @RouteFeatures int getOptionalFeatures() {
+ return mOptionalFeatures;
+ }
+
+ /**
+ * Gets the list of route protocols that a route must support in order to be selected.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @return The list of fully qualified route protocol names.
+ */
+ public @NonNull List<String> getRequiredProtocols() {
+ return mRequiredProtocols;
+ }
+
+ /**
+ * Gets the list of optional route protocols that a client may use if they are available.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @return The list of optional fully qualified route protocol names.
+ */
+ public @NonNull List<String> getOptionalProtocols() {
+ return mOptionalProtocols;
+ }
+
+ /**
+ * Returns true if the selector includes a required or optional request for
+ * the specified protocol using its fully qualified class name.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param clazz The protocol class.
+ * @return True if the protocol was requested.
+ */
+ public boolean containsProtocol(@NonNull Class<?> clazz) {
+ return containsProtocol(clazz.getName());
+ }
+
+ /**
+ * Returns true if the selector includes a required or optional request for
+ * the specified protocol.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param name The name of the protocol.
+ * @return True if the protocol was requested.
+ */
+ public boolean containsProtocol(@NonNull String name) {
+ return mRequiredProtocols.contains(name)
+ || mOptionalProtocols.contains(name);
+ }
+
+ /**
+ * Gets the package name of a specific media route service that this route selector
+ * requires.
+ *
+ * @return The required media route service package name, or null if none.
+ */
+ public @Nullable String getServicePackageName() {
+ return mServicePackageName;
+ }
+
+ /**
+ * Gets optional extras that may be used to select or configure routes for a
+ * particular purpose. Some extras may be used by media route services to apply
+ * additional constraints or parameters for the routes to be discovered.
+ *
+ * @return The optional extras, or null if none.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public String toString() {
+ return "MediaRouteSelector{ "
+ + ", requiredFeatures=0x" + Integer.toHexString(mRequiredFeatures)
+ + ", optionalFeatures=0x" + Integer.toHexString(mOptionalFeatures)
+ + ", requiredProtocols=" + mRequiredProtocols
+ + ", optionalProtocols=" + mOptionalProtocols
+ + ", servicePackageName=" + mServicePackageName
+ + ", extras=" + mExtras + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRequiredFeatures);
+ dest.writeInt(mOptionalFeatures);
+ dest.writeStringList(mRequiredProtocols);
+ dest.writeStringList(mOptionalProtocols);
+ dest.writeString(mServicePackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Parcelable.Creator<MediaRouteSelector> CREATOR =
+ new Parcelable.Creator<MediaRouteSelector>() {
+ @Override
+ public MediaRouteSelector createFromParcel(Parcel source) {
+ int requiredFeatures = source.readInt();
+ int optionalFeatures = source.readInt();
+ ArrayList<String> requiredProtocols = new ArrayList<String>();
+ ArrayList<String> optionalProtocols = new ArrayList<String>();
+ source.readStringList(requiredProtocols);
+ source.readStringList(optionalProtocols);
+ return new MediaRouteSelector(requiredFeatures, optionalFeatures,
+ requiredProtocols, optionalProtocols,
+ source.readString(), source.readBundle());
+ }
+
+ @Override
+ public MediaRouteSelector[] newArray(int size) {
+ return new MediaRouteSelector[size];
+ }
+ };
+
+ /**
+ * Builder for {@link MediaRouteSelector} objects.
+ */
+ public static final class Builder {
+ private int mRequiredFeatures;
+ private int mOptionalFeatures;
+ private final ArrayList<String> mRequiredProtocols = new ArrayList<String>();
+ private final ArrayList<String> mOptionalProtocols = new ArrayList<String>();
+ private String mServicePackageName;
+ private Bundle mExtras;
+
+ /**
+ * Creates an initially empty selector builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the set of required route features.
+ *
+ * @param features A set of required route feature flags.
+ */
+ public @NonNull Builder setRequiredFeatures(@RouteFeatures int features) {
+ mRequiredFeatures = features;
+ return this;
+ }
+
+ /**
+ * Sets the set of optional route features.
+ *
+ * @param features A set of optional route feature flags.
+ */
+ public @NonNull Builder setOptionalFeatures(@RouteFeatures int features) {
+ mOptionalFeatures = features;
+ return this;
+ }
+
+ /**
+ * Adds a route protocol that a route must support in order to be selected
+ * using its fully qualified class name.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param clazz The protocol class.
+ * @return this
+ */
+ public @NonNull Builder addRequiredProtocol(@NonNull Class<?> clazz) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("clazz must not be null");
+ }
+ return addRequiredProtocol(clazz.getName());
+ }
+
+ /**
+ * Adds a route protocol that a route must support in order to be selected.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param name The fully qualified name of the required protocol.
+ * @return this
+ */
+ public @NonNull Builder addRequiredProtocol(@NonNull String name) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null or empty");
+ }
+ mRequiredProtocols.add(name);
+ return this;
+ }
+
+ /**
+ * Adds an optional route protocol that a client may use if available
+ * using its fully qualified class name.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param clazz The protocol class.
+ * @return this
+ */
+ public @NonNull Builder addOptionalProtocol(@NonNull Class<?> clazz) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("clazz must not be null");
+ }
+ return addOptionalProtocol(clazz.getName());
+ }
+
+ /**
+ * Adds an optional route protocol that a client may use if available.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ *
+ * @param name The fully qualified name of the optional protocol.
+ * @return this
+ */
+ public @NonNull Builder addOptionalProtocol(@NonNull String name) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null or empty");
+ }
+ mOptionalProtocols.add(name);
+ return this;
+ }
+
+ /**
+ * Sets the package name of the media route service to which this selector
+ * appertains.
+ * <p>
+ * If a package name is specified here then this selector will only be
+ * passed to media route services from that package. This has the effect
+ * of restricting the set of matching routes to just those that are offered
+ * by that package.
+ * </p>
+ *
+ * @param packageName The required service package name, or null if none.
+ * @return this
+ */
+ public @NonNull Builder setServicePackageName(@Nullable String packageName) {
+ mServicePackageName = packageName;
+ return this;
+ }
+
+ /**
+ * Sets optional extras that may be used to select or configure routes for a
+ * particular purpose. Some extras may be used by route services to specify
+ * additional constraints or parameters for the routes to be discovered.
+ *
+ * @param extras The optional extras, or null if none.
+ * @return this
+ */
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaRouteSelector} object.
+ *
+ * @return The new media route selector instance.
+ */
+ public @NonNull MediaRouteSelector build() {
+ return new MediaRouteSelector(mRequiredFeatures, mOptionalFeatures,
+ mRequiredProtocols, mOptionalProtocols, mServicePackageName, mExtras);
+ }
+ }
+}
diff --git a/media/java/android/media/routing/MediaRouteService.java b/media/java/android/media/routing/MediaRouteService.java
new file mode 100644
index 0000000..4d5a8a9
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouteService.java
@@ -0,0 +1,1023 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.routing.MediaRouter.ConnectionError;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryError;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
+import android.media.routing.MediaRouter.ServiceMetadata;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media route services implement strategies for discovering
+ * and establishing connections to media devices and their routes. These services
+ * are also known as media route providers.
+ * <p>
+ * Each media route service subclass is responsible for enabling applications
+ * and the system to interact with media devices of some kind.
+ * For example, one particular media route service implementation might
+ * offer support for discovering nearby wireless display devices and streaming
+ * video contents to them; another media route service implementation might
+ * offer support for discovering nearby speakers and streaming media appliances
+ * and sending commands to play content on request.
+ * </p><p>
+ * Subclasses must override the {@link #onCreateClientSession} method to return
+ * a {@link ClientSession} object that implements the {@link ClientSession#onStartDiscovery},
+ * {@link ClientSession#onStopDiscovery}, and {@link ClientSession#onConnect} methods
+ * to allow clients to discover and connect to media devices.
+ * </p><p>
+ * This object is not thread-safe. All callbacks are invoked on the main looper.
+ * </p>
+ *
+ * <h3>Clients</h3>
+ * <p>
+ * The clients of this API are media applications that would like to discover
+ * and connect to media devices. The client may also be the system, such as
+ * when the user initiates display mirroring via the Cast Screen function.
+ * </p><p>
+ * There may be multiple client sessions active at the same time. Each client
+ * session can request discovery and connect to routes independently of any
+ * other client. It is the responsibility of the media route service to maintain
+ * separate state for each client session and to ensure that clients cannot interfere
+ * with one another in harmful ways.
+ * </p><p>
+ * Notwithstanding the requirement to support any number of concurrent client
+ * sessions, the media route service may impose constraints on how many clients
+ * can connect to the same media device in a particular mode at the same time.
+ * In some cases, media devices may support connections from an arbitrary number
+ * of clients simultaneously but often it may be necessary to ensure that only
+ * one client is in control. When this happens, the media route service should
+ * report a connection error unless the connection request specifies that the
+ * client should take control of the media device (and forcibly disconnect other
+ * clients that may be using it).
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations. Each destination therefore represents a single
+ * independent device such as a speaker or TV set. Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly. The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination. Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time. For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Discovery</h3>
+ * <p>
+ * Discovery is the process of finding destinations based on a description of the
+ * kinds of routes that an application or the system would like to use.
+ * </p><p>
+ * Discovery begins when {@link ClientSession#onStartDiscovery} is called and ends when
+ * {@link ClientSession#onStopDiscovery} is called. There may be multiple simultaneous
+ * discovery requests in progress at the same time from different clients. It is up to
+ * the media route service to perform these requests in parallel or multiplex them
+ * as required.
+ * </p><p>
+ * Media route services are <em>strongly encouraged</em> to use the information
+ * in the discovery request to optimize discovery and avoid redundant work.
+ * In the case where no media device supported by the media route service
+ * could possibly offer the requested capabilities, the
+ * {@link ClientSession#onStartDiscovery} method should return <code>false</code> to
+ * let the system know that it can unbind from the media route service and
+ * release its resources.
+ * </p>
+ *
+ * <h3>Settings</h3>
+ * <p>
+ * Many kinds of devices can be discovered on demand simply by scanning the local network
+ * or using wireless protocols such as Bluetooth to find them. However, in some cases
+ * it may be necessary for the user to manually configure destinations before they
+ * can be used (or to adjust settings later). Actual user configuration of destinations
+ * is beyond the scope of this API but media route services may specify an activity
+ * in their manifest that the user can launch to perform these tasks.
+ * </p><p>
+ * Note that media route services that are installed from the store must be enabled
+ * by the user before they become available for applications to use.
+ * The {@link android.provider.Settings#ACTION_CAST_SETTINGS Settings.ACTION_CAST_SETTINGS}
+ * settings activity provides the ability for the user to configure media route services.
+ * </p>
+ *
+ * <h3>Manifest Declaration</h3>
+ * <p>
+ * Media route services must be declared in the manifest along with meta-data
+ * about the kinds of routes that they are capable of discovering. The system
+ * uses this information to optimize the set of services to which it binds in
+ * order to satisfy a particular discovery request.
+ * </p><p>
+ * To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_MEDIA_ROUTE_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. You must
+ * also add meta-data to describe the kinds of routes that your service is capable
+ * of discovering.
+ * </p><p>
+ * For example:
+ * </p><pre>
+ * &lt;service android:name=".MediaRouteProvider"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.media.routing.MediaRouteService" />
+ * &lt;/intent-filter>
+ *
+ * TODO: INSERT METADATA DECLARATIONS HERE
+ *
+ * &lt;/service>
+ * </pre>
+ */
+public abstract class MediaRouteService extends Service {
+ private static final String TAG = "MediaRouteService";
+
+ private static final boolean DEBUG = true;
+
+ private final Handler mHandler;
+ private final BinderService mService;
+ private final ArrayMap<IBinder, ClientRecord> mClientRecords =
+ new ArrayMap<IBinder, ClientRecord>();
+
+ private ServiceMetadata mMetadata;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.media.routing.MediaRouteService";
+
+ /**
+ * Creates a media route service.
+ */
+ public MediaRouteService() {
+ mHandler = new Handler(true);
+ mService = new BinderService();
+ }
+
+ @Override
+ public @Nullable IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mService;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new client session on behalf of a client.
+ * <p>
+ * The implementation should return a {@link ClientSession} for the client
+ * to use. The media route service must take care to manage the state of
+ * each client session independently from any others that might also be
+ * in use at the same time.
+ * </p>
+ *
+ * @param client Information about the client.
+ * @return The client session object, or null if the client is not allowed
+ * to interact with this media route service.
+ */
+ public abstract @Nullable ClientSession onCreateClientSession(@NonNull ClientInfo client);
+
+ /**
+ * Gets metadata about this service.
+ * <p>
+ * Use this method to obtain a {@link ServiceMetadata} object to provide when creating
+ * a {@link android.media.routing.MediaRouter.DestinationInfo.Builder}.
+ * </p>
+ *
+ * @return Metadata about this service.
+ */
+ public @NonNull ServiceMetadata getServiceMetadata() {
+ if (mMetadata == null) {
+ try {
+ mMetadata = new ServiceMetadata(this);
+ } catch (NameNotFoundException ex) {
+ Log.wtf(TAG, "Could not retrieve own service metadata!");
+ }
+ }
+ return mMetadata;
+ }
+
+ /**
+ * Enables a single client to access the functionality of the media route service.
+ */
+ public static abstract class ClientSession {
+ /**
+ * Starts discovery.
+ * <p>
+ * If the media route service is capable of discovering routes that satisfy
+ * the request then this method should start discovery and return true.
+ * Otherwise, this method should return false. If false is returned,
+ * then the framework will not call {@link #onStopDiscovery} since discovery
+ * was never actually started.
+ * </p><p>
+ * There may already be other discovery requests in progress at the same time
+ * for other clients; the media route service must keep track of them all.
+ * </p>
+ *
+ * @param req The discovery request to start.
+ * @param callback A callback to receive discovery events related to this
+ * particular request. The events that the service sends to this callback
+ * will be sent to the client that initiated the discovery request.
+ * @return True if discovery has started. False if the media route service
+ * is unable to discover routes that satisfy the request.
+ */
+ public abstract boolean onStartDiscovery(@NonNull DiscoveryRequest req,
+ @NonNull DiscoveryCallback callback);
+
+ /**
+ * Stops discovery.
+ * <p>
+ * If {@link #onStartDiscovery} returned true, then this method will eventually
+ * be called when the framework no longer requires this discovery request
+ * to be performed.
+ * </p><p>
+ * There may still be other discovery requests in progress for other clients;
+ * they must keep working until they have each been stopped by their client.
+ * </p>
+ */
+ public abstract void onStopDiscovery();
+
+ /**
+ * Starts connecting to a route.
+ *
+ * @param req The connection request.
+ * @param callback A callback to receive events connection events related
+ * to this particular request. The events that the service sends to this callback
+ * will be sent to the client that initiated the discovery request.
+ * @return True if the connection is in progress, or false if the client
+ * unable to connect to the requested route.
+ */
+ public abstract boolean onConnect(@NonNull ConnectionRequest req,
+ @NonNull ConnectionCallback callback);
+
+ /**
+ * Called when the client requests to disconnect from the route
+ * or abort a connection attempt in progress.
+ */
+ public abstract void onDisconnect();
+
+ /**
+ * Called when the client requests to pause streaming of content to
+ * live audio/video routes such as when it goes into the background.
+ * <p>
+ * The default implementation does nothing.
+ * </p>
+ */
+ public void onPauseStream() { }
+
+ /**
+ * Called when the application requests to resume streaming of content to
+ * live audio/video routes such as when it returns to the foreground.
+ * <p>
+ * The default implementation does nothing.
+ * </p>
+ */
+ public void onResumeStream() { }
+
+ /**
+ * Called when the client is releasing the session.
+ * <p>
+ * The framework automatically takes care of stopping discovery and
+ * terminating the connection politely before calling this method to release
+ * the session.
+ * </p><p>
+ * The default implementation does nothing.
+ * </p>
+ */
+ public void onRelease() { }
+ }
+
+ /**
+ * Provides events in response to a discovery request.
+ */
+ public final class DiscoveryCallback {
+ private final ClientRecord mRecord;
+
+ DiscoveryCallback(ClientRecord record) {
+ mRecord = record;
+ }
+
+ /**
+ * Called by the service when a destination is found that
+ * offers one or more routes that satisfy the discovery request.
+ * <p>
+ * This method should be called whenever the list of available routes
+ * at a destination changes or whenever the properties of the destination
+ * itself change.
+ * </p>
+ *
+ * @param destination The destination that was found.
+ * @param routes The list of that destination's routes that satisfy the
+ * discovery request.
+ */
+ public void onDestinationFound(final @NonNull DestinationInfo destination,
+ final @NonNull List<RouteInfo> routes) {
+ if (destination == null) {
+ throw new IllegalArgumentException("destination must not be null");
+ }
+ if (routes == null) {
+ throw new IllegalArgumentException("routes must not be null");
+ }
+ for (int i = 0; i < routes.size(); i++) {
+ if (routes.get(i).getDestination() != destination) {
+ throw new IllegalArgumentException("routes must refer to the "
+ + "destination");
+ }
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchDestinationFound(DiscoveryCallback.this,
+ destination, routes);
+ }
+ });
+ }
+
+ /**
+ * Called by the service when a destination is no longer
+ * reachable or is no longer offering any routes that satisfy
+ * the discovery request.
+ *
+ * @param destination The destination that went away.
+ */
+ public void onDestinationLost(final @NonNull DestinationInfo destination) {
+ if (destination == null) {
+ throw new IllegalArgumentException("destination must not be null");
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchDestinationLost(DiscoveryCallback.this, destination);
+ }
+ });
+ }
+
+ /**
+ * Called by the service when a discovery has failed in a non-recoverable manner.
+ *
+ * @param error The error code: one of
+ * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+ * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+ * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+ * @param message The localized error message, or null if none. This message
+ * may be shown to the user.
+ * @param extras Additional information about the error which a client
+ * may use, or null if none.
+ */
+ public void onDiscoveryFailed(final @DiscoveryError int error,
+ final @Nullable CharSequence message, final @Nullable Bundle extras) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchDiscoveryFailed(DiscoveryCallback.this,
+ error, message, extras);
+ }
+ });
+ }
+ }
+
+ /**
+ * Provides events in response to a connection request.
+ */
+ public final class ConnectionCallback {
+ private final ClientRecord mRecord;
+
+ ConnectionCallback(ClientRecord record) {
+ mRecord = record;
+ }
+
+ /**
+ * Called by the service when the connection succeeds.
+ *
+ * @param connection Immutable information about the connection.
+ */
+ public void onConnected(final @NonNull ConnectionInfo connection) {
+ if (connection == null) {
+ throw new IllegalArgumentException("connection must not be null");
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchConnected(ConnectionCallback.this, connection);
+ }
+ });
+ }
+
+ /**
+ * Called by the service when the connection is terminated normally.
+ * <p>
+ * Abnormal termination is reported via {@link #onConnectionFailed}.
+ * </p>
+ */
+ public void onDisconnected() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchDisconnected(ConnectionCallback.this);
+ }
+ });
+ }
+
+ /**
+ * Called by the service when a connection attempt or connection in
+ * progress has failed in a non-recoverable manner.
+ *
+ * @param error The error code: one of
+ * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+ * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+ * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+ * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+ * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+ * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+ * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+ * @param message The localized error message, or null if none. This message
+ * may be shown to the user.
+ * @param extras Additional information about the error which a client
+ * may use, or null if none.
+ */
+ public void onConnectionFailed(final @ConnectionError int error,
+ final @Nullable CharSequence message, final @Nullable Bundle extras) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mRecord.dispatchConnectionFailed(ConnectionCallback.this,
+ error, message, extras);
+ }
+ });
+ }
+ }
+
+ /**
+ * Identifies a client of the media route service.
+ */
+ public static final class ClientInfo {
+ private final int mUid;
+ private final String mPackageName;
+
+ ClientInfo(int uid, String packageName) {
+ mUid = uid;
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the UID of the client application.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Gets the package name of the client application.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "ClientInfo{ uid=" + mUid + ", package=" + mPackageName + " }";
+ }
+ }
+
+ private final class BinderService extends IMediaRouteService.Stub {
+ @Override
+ public void registerClient(final int clientUid, final String clientPackageName,
+ final IMediaRouteClientCallback callback) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientInfo client = new ClientInfo(clientUid, clientPackageName);
+ if (DEBUG) {
+ Log.d(TAG, "registerClient: client=" + client);
+ }
+
+ ClientSession session = onCreateClientSession(client);
+ if (session == null) {
+ // request refused by service
+ Log.w(TAG, "Media route service refused to create session for client: "
+ + "client=" + client);
+ return;
+ }
+
+ ClientRecord record = new ClientRecord(callback, client, session);
+ try {
+ callback.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException ex) {
+ // client died prematurely
+ Log.w(TAG, "Client died prematurely while creating session: "
+ + "client=" + client);
+ record.release();
+ return;
+ }
+
+ mClientRecords.put(callback.asBinder(), record);
+ }
+ });
+ }
+
+ @Override
+ public void unregisterClient(IMediaRouteClientCallback callback) {
+ unregisterClient(callback, false);
+ }
+
+ void unregisterClient(final IMediaRouteClientCallback callback,
+ final boolean died) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.remove(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "unregisterClient: client=" + record.getClientInfo()
+ + ", died=" + died);
+ }
+
+ record.release();
+ callback.asBinder().unlinkToDeath(record, 0);
+ }
+ });
+ }
+
+ @Override
+ public void startDiscovery(final IMediaRouteClientCallback callback,
+ final int seq, final List<MediaRouteSelector> selectors,
+ final int flags) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "startDiscovery: client=" + record.getClientInfo()
+ + ", seq=" + seq + ", selectors=" + selectors
+ + ", flags=0x" + Integer.toHexString(flags));
+ }
+ record.startDiscovery(seq, selectors, flags);
+ }
+ });
+ }
+
+ @Override
+ public void stopDiscovery(final IMediaRouteClientCallback callback) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "stopDiscovery: client=" + record.getClientInfo());
+ }
+ record.stopDiscovery();
+ }
+ });
+ }
+
+ @Override
+ public void connect(final IMediaRouteClientCallback callback,
+ final int seq, final String destinationId, final String routeId,
+ final int flags, final Bundle extras) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "connect: client=" + record.getClientInfo()
+ + ", seq=" + seq + ", destinationId=" + destinationId
+ + ", routeId=" + routeId
+ + ", flags=0x" + Integer.toHexString(flags)
+ + ", extras=" + extras);
+ }
+ record.connect(seq, destinationId, routeId, flags, extras);
+ }
+ });
+ }
+
+ @Override
+ public void disconnect(final IMediaRouteClientCallback callback) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "disconnect: client=" + record.getClientInfo());
+ }
+ record.disconnect();
+ }
+ });
+ }
+
+ @Override
+ public void pauseStream(final IMediaRouteClientCallback callback) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "pauseStream: client=" + record.getClientInfo());
+ }
+ record.pauseStream();
+ }
+ });
+ }
+
+ @Override
+ public void resumeStream(final IMediaRouteClientCallback callback) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ClientRecord record = mClientRecords.get(callback.asBinder());
+ if (record == null) {
+ return; // spurious
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "resumeStream: client=" + record.getClientInfo());
+ }
+ record.resumeStream();
+ }
+ });
+ }
+ }
+
+ // Must be accessed on handler
+ private final class ClientRecord implements IBinder.DeathRecipient {
+ private final IMediaRouteClientCallback mClientCallback;
+ private final ClientInfo mClient;
+ private final ClientSession mSession;
+
+ private int mDiscoverySeq;
+ private DiscoveryRequest mDiscoveryRequest;
+ private DiscoveryCallback mDiscoveryCallback;
+ private final ArrayMap<String, DestinationRecord> mDestinations =
+ new ArrayMap<String, DestinationRecord>();
+
+ private int mConnectionSeq;
+ private ConnectionRequest mConnectionRequest;
+ private ConnectionCallback mConnectionCallback;
+ private ConnectionInfo mConnection;
+ private boolean mConnectionPaused;
+
+ public ClientRecord(IMediaRouteClientCallback callback,
+ ClientInfo client, ClientSession session) {
+ mClientCallback = callback;
+ mClient = client;
+ mSession = session;
+ }
+
+ // Invoked on binder thread unlike all other methods in this class.
+ @Override
+ public void binderDied() {
+ mService.unregisterClient(mClientCallback, true);
+ }
+
+ public ClientInfo getClientInfo() {
+ return mClient;
+ }
+
+ public void release() {
+ stopDiscovery();
+ disconnect();
+ }
+
+ public void startDiscovery(int seq, List<MediaRouteSelector> selectors,
+ int flags) {
+ stopDiscovery();
+
+ mDiscoverySeq = seq;
+ mDiscoveryRequest = new DiscoveryRequest(selectors);
+ mDiscoveryRequest.setFlags(flags);
+ mDiscoveryCallback = new DiscoveryCallback(this);
+ boolean started = mSession.onStartDiscovery(mDiscoveryRequest, mDiscoveryCallback);
+ if (!started) {
+ dispatchDiscoveryFailed(mDiscoveryCallback,
+ MediaRouter.DISCOVERY_ERROR_ABORTED, null, null);
+ clearDiscovery();
+ }
+ }
+
+ public void stopDiscovery() {
+ if (mDiscoveryRequest != null) {
+ mSession.onStopDiscovery();
+ clearDiscovery();
+ }
+ }
+
+ private void clearDiscovery() {
+ mDestinations.clear();
+ mDiscoveryRequest = null;
+ mDiscoveryCallback = null;
+ }
+
+ public void connect(int seq, String destinationId, String routeId,
+ int flags, Bundle extras) {
+ disconnect();
+
+ mConnectionSeq = seq;
+ mConnectionCallback = new ConnectionCallback(this);
+
+ DestinationRecord destinationRecord = mDestinations.get(destinationId);
+ if (destinationRecord == null) {
+ Log.w(TAG, "Aborting connection to route since no matching destination "
+ + "was found in the list of known destinations: "
+ + "destinationId=" + destinationId);
+ dispatchConnectionFailed(mConnectionCallback,
+ MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+ clearConnection();
+ return;
+ }
+
+ RouteInfo route = destinationRecord.getRoute(routeId);
+ if (route == null) {
+ Log.w(TAG, "Aborting connection to route since no matching route "
+ + "was found in the list of known routes: "
+ + "destination=" + destinationRecord.destination
+ + ", routeId=" + routeId);
+ dispatchConnectionFailed(mConnectionCallback,
+ MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+ clearConnection();
+ return;
+ }
+
+ mConnectionRequest = new ConnectionRequest(route);
+ mConnectionRequest.setFlags(flags);
+ mConnectionRequest.setExtras(extras);
+ boolean started = mSession.onConnect(mConnectionRequest, mConnectionCallback);
+ if (!started) {
+ dispatchConnectionFailed(mConnectionCallback,
+ MediaRouter.CONNECTION_ERROR_ABORTED, null, null);
+ clearConnection();
+ }
+ }
+
+ public void disconnect() {
+ if (mConnectionRequest != null) {
+ mSession.onDisconnect();
+ clearConnection();
+ }
+ }
+
+ private void clearConnection() {
+ mConnectionRequest = null;
+ mConnectionCallback = null;
+ if (mConnection != null) {
+ mConnection.close();
+ mConnection = null;
+ }
+ mConnectionPaused = false;
+ }
+
+ public void pauseStream() {
+ if (mConnectionRequest != null && !mConnectionPaused) {
+ mConnectionPaused = true;
+ mSession.onPauseStream();
+ }
+ }
+
+ public void resumeStream() {
+ if (mConnectionRequest != null && mConnectionPaused) {
+ mConnectionPaused = false;
+ mSession.onResumeStream();
+ }
+ }
+
+ public void dispatchDestinationFound(DiscoveryCallback callback,
+ DestinationInfo destination, List<RouteInfo> routes) {
+ if (callback == mDiscoveryCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "destinationFound: destination=" + destination
+ + ", routes=" + routes);
+ }
+ mDestinations.put(destination.getId(),
+ new DestinationRecord(destination, routes));
+
+ ParcelableDestinationInfo pdi = new ParcelableDestinationInfo();
+ pdi.id = destination.getId();
+ pdi.name = destination.getName();
+ pdi.description = destination.getDescription();
+ pdi.iconResourceId = destination.getIconResourceId();
+ pdi.extras = destination.getExtras();
+ ArrayList<ParcelableRouteInfo> pris = new ArrayList<ParcelableRouteInfo>();
+ for (RouteInfo route : routes) {
+ int selectorIndex = mDiscoveryRequest.getSelectors().indexOf(
+ route.getSelector());
+ if (selectorIndex < 0) {
+ Log.w(TAG, "Ignoring route because the selector does not match "
+ + "any of those that were originally supplied by the "
+ + "client's discovery request: destination=" + destination
+ + ", route=" + route);
+ continue;
+ }
+
+ ParcelableRouteInfo pri = new ParcelableRouteInfo();
+ pri.id = route.getId();
+ pri.selectorIndex = selectorIndex;
+ pri.features = route.getFeatures();
+ pri.protocols = route.getProtocols().toArray(
+ new String[route.getProtocols().size()]);
+ pri.extras = route.getExtras();
+ pris.add(pri);
+ }
+ try {
+ mClientCallback.onDestinationFound(mDiscoverySeq, pdi,
+ pris.toArray(new ParcelableRouteInfo[pris.size()]));
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ }
+ }
+
+ public void dispatchDestinationLost(DiscoveryCallback callback,
+ DestinationInfo destination) {
+ if (callback == mDiscoveryCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "destinationLost: destination=" + destination);
+ }
+
+ if (mDestinations.get(destination.getId()).destination == destination) {
+ mDestinations.remove(destination.getId());
+ try {
+ mClientCallback.onDestinationLost(mDiscoverySeq, destination.getId());
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ }
+ }
+ }
+
+ public void dispatchDiscoveryFailed(DiscoveryCallback callback,
+ int error, CharSequence message, Bundle extras) {
+ if (callback == mDiscoveryCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "discoveryFailed: error=" + error + ", message=" + message
+ + ", extras=" + extras);
+ }
+
+ try {
+ mClientCallback.onDiscoveryFailed(mDiscoverySeq, error, message, extras);
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ }
+ }
+
+ public void dispatchConnected(ConnectionCallback callback, ConnectionInfo connection) {
+ if (callback == mConnectionCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "connected: connection=" + connection);
+ }
+ if (mConnection == null) {
+ mConnection = connection;
+
+ ParcelableConnectionInfo pci = new ParcelableConnectionInfo();
+ pci.audioAttributes = connection.getAudioAttributes();
+ pci.presentationDisplayId = connection.getPresentationDisplay() != null ?
+ connection.getPresentationDisplay().getDisplayId() : -1;
+ pci.protocolBinders = new IBinder[connection.getProtocols().size()];
+ for (int i = 0; i < pci.protocolBinders.length; i++) {
+ pci.protocolBinders[i] = connection.getProtocolBinder(i);
+ }
+ pci.extras = connection.getExtras();
+ try {
+ mClientCallback.onConnected(mConnectionSeq, pci);
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ } else {
+ Log.w(TAG, "Media route service called onConnected() while already "
+ + "connected.");
+ }
+ }
+ }
+
+ public void dispatchDisconnected(ConnectionCallback callback) {
+ if (callback == mConnectionCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "disconnected");
+ }
+
+ if (mConnection != null) {
+ mConnection.close();
+ mConnection = null;
+
+ try {
+ mClientCallback.onDisconnected(mConnectionSeq);
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ }
+ }
+ }
+
+ public void dispatchConnectionFailed(ConnectionCallback callback,
+ int error, CharSequence message, Bundle extras) {
+ if (callback == mConnectionCallback) {
+ if (DEBUG) {
+ Log.d(TAG, "connectionFailed: error=" + error + ", message=" + message
+ + ", extras=" + extras);
+ }
+
+ try {
+ mClientCallback.onConnectionFailed(mConnectionSeq, error, message, extras);
+ } catch (RemoteException ex) {
+ // binder death handled elsewhere
+ }
+ }
+ }
+ }
+
+ private static final class DestinationRecord {
+ public final DestinationInfo destination;
+ public final List<RouteInfo> routes;
+
+ public DestinationRecord(DestinationInfo destination, List<RouteInfo> routes) {
+ this.destination = destination;
+ this.routes = routes;
+ }
+
+ public RouteInfo getRoute(String routeId) {
+ final int count = routes.size();
+ for (int i = 0; i < count; i++) {
+ RouteInfo route = routes.get(i);
+ if (route.getId().equals(routeId)) {
+ return route;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/media/java/android/media/routing/MediaRouter.java b/media/java/android/media/routing/MediaRouter.java
new file mode 100644
index 0000000..4f6d324
--- /dev/null
+++ b/media/java/android/media/routing/MediaRouter.java
@@ -0,0 +1,1886 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Presentation;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.Display;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Media router allows applications to discover, connect to, control,
+ * and send content to nearby media devices known as destinations.
+ * <p>
+ * There are generally two participants involved in media routing: an
+ * application that wants to send media content to a destination and a
+ * {@link MediaRouteService media route service} that provides the
+ * service of transporting that content where it needs to go on behalf of the
+ * application.
+ * </p><p>
+ * To send media content to a destination, the application must ask the system
+ * to discover available routes to destinations that provide certain capabilities,
+ * establish a connection to a route, then send messages through the connection to
+ * control the routing of audio and video streams, launch remote applications,
+ * and invoke other functions of the destination.
+ * </p><p>
+ * Media router objects are thread-safe.
+ * </p>
+ *
+ * <h3>Destinations</h3>
+ * <p>
+ * The media devices to which an application may send media content are referred
+ * to in the API as destinations. Each destination therefore represents a single
+ * independent device such as a speaker or TV set. Destinations are given meaningful
+ * names and descriptions to help the user associate them with devices in their
+ * environment.
+ * </p><p>
+ * Destinations may be local or remote and may be accessed through various means,
+ * often wirelessly. The user may install media route services to enable
+ * media applications to connect to a variety of destinations with different
+ * capabilities.
+ * </p>
+ *
+ * <h3>Routes</h3>
+ * <p>
+ * Routes represent possible usages or means of reaching and interacting with
+ * a destination. Since destinations may support many different features, they may
+ * each offer multiple routes for applications to choose from based on their needs.
+ * For example, one route might express the ability to stream locally rendered audio
+ * and video to the device; another route might express the ability to send a URL for
+ * the destination to download from the network and play all by itself.
+ * </p><p>
+ * Routes are discovered according to the set of capabilities that
+ * an application or the system is seeking to use at a particular time. For example,
+ * if an application wants to stream music to a destination then it will ask the
+ * {@link MediaRouter} to find routes to destinations can stream music and ignore
+ * all other destinations that cannot.
+ * </p><p>
+ * In general, the application will inspect the set of routes that have been
+ * offered then connect to the most appropriate route for its desired purpose.
+ * </p>
+ *
+ * <h3>Route Selection</h3>
+ * <p>
+ * When the user open the media route chooser activity, the system will display
+ * a list of nearby media destinations which have been discovered. After the
+ * choice is made the application may connect to one of the routes offered by
+ * this destination and begin communicating with the destination.
+ * </p><p>
+ * Destinations are located through a process called discovery. During discovery,
+ * the system will start installed {@link MediaRouteService media route services}
+ * to scan the network for nearby devices that offer the kinds of capabilities that the
+ * application is seeking to use. The application specifies the capabilities it requires by
+ * adding {@link MediaRouteSelector media route selectors} to the media router
+ * using the {@link #addSelector} method. Only destinations that provide routes
+ * which satisfy at least one of these media route selectors will be discovered.
+ * </p><p>
+ * Once the user has selected a destination, the application will be given a chance
+ * to choose one of the routes to which it would like to connect. The application
+ * may switch to a different route from the same destination at a later time but
+ * in order to connect to a new destination, the application must once again launch
+ * the media route chooser activity to ask the user to choose a destination.
+ * </p>
+ *
+ * <h3>Route Protocols</h3>
+ * <p>
+ * Route protocols express capabilities offered by routes. Each media route selector
+ * must specify at least one required protocol by which the routes will be selected.
+ * </p><p>
+ * The framework provides several predefined <code>MediaRouteProtocols</code> which are
+ * defined in the <code>android-support-media-protocols.jar</code> support library.
+ * Applications must statically link this library to make use of these protocols.
+ * </p><p>
+ * The static library approach is used to enable ongoing extension and refinement
+ * of protocols in the SDK and interoperability with the media router implementation
+ * for older platform versions which is offered by the framework support library.
+ * </p><p>
+ * Media route services may also define custom media route protocols of their own
+ * to enable applications to access specialized capabilities of certain destinations
+ * assuming they have linked in the required protocol code.
+ * </p><p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code> for more information.
+ * </p>
+ *
+ * <h3>Connections</h3>
+ * <p>
+ * After connecting to a media route, the application can send commands to
+ * the route using any of the protocols that it requested. If the route supports live
+ * audio or video streaming then the application can create an {@link AudioTrack} or
+ * {@link Presentation} to route locally generated content to the destination.
+ * </p>
+ *
+ * <h3>Delegation</h3>
+ * <p>
+ * The creator of the media router is responsible for establishing the policy for
+ * discovering and connecting to destinations. UI components may observe the state
+ * of the media router by {@link #createDelegate creating} a {@link Delegate}.
+ * </p><p>
+ * The media router should also be attached to the {@link MediaSession media session}
+ * that is handling media playback lifecycle. This will allow
+ * authorized {@link MediaController media controllers}, possibly running in other
+ * processes, to provide UI to examine and change the media destination by
+ * {@link MediaController#createMediaRouterDelegate creating} a {@link Delegate}
+ * for the media router associated with the session.
+ * </p>
+ */
+public final class MediaRouter {
+ private final DisplayManager mDisplayManager;
+
+ private final Object mLock = new Object();
+
+ private RoutingCallback mRoutingCallback;
+ private Handler mRoutingCallbackHandler;
+
+ private boolean mReleased;
+ private int mDiscoveryState;
+ private int mConnectionState;
+ private final ArrayList<MediaRouteSelector> mSelectors =
+ new ArrayList<MediaRouteSelector>();
+ private final ArrayMap<DestinationInfo, List<RouteInfo>> mDiscoveredDestinations =
+ new ArrayMap<DestinationInfo, List<RouteInfo>>();
+ private RouteInfo mSelectedRoute;
+ private ConnectionInfo mConnection;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = { DISCOVERY_STATE_STOPPED, DISCOVERY_STATE_STARTED })
+ public @interface DiscoveryState { }
+
+ /**
+ * Discovery state: Discovery is not currently in progress.
+ */
+ public static final int DISCOVERY_STATE_STOPPED = 0;
+
+ /**
+ * Discovery state: Discovery is being performed.
+ */
+ public static final int DISCOVERY_STATE_STARTED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = { DISCOVERY_FLAG_BACKGROUND })
+ public @interface DiscoveryFlags { }
+
+ /**
+ * Discovery flag: Indicates that the client has requested passive discovery in
+ * the background. The media route service should try to use less power and rely
+ * more on its internal caches to minimize its impact.
+ */
+ public static final int DISCOVERY_FLAG_BACKGROUND = 1 << 0;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = { DISCOVERY_ERROR_UNKNOWN, DISCOVERY_ERROR_ABORTED,
+ DISCOVERY_ERROR_NO_CONNECTIVITY })
+ public @interface DiscoveryError { }
+
+ /**
+ * Discovery error: Unknown error; refer to the error message for details.
+ */
+ public static final int DISCOVERY_ERROR_UNKNOWN = 0;
+
+ /**
+ * Discovery error: The media router or media route service has decided not to
+ * handle the discovery request for some reason.
+ */
+ public static final int DISCOVERY_ERROR_ABORTED = 1;
+
+ /**
+ * Discovery error: The media route service is unable to perform discovery
+ * due to a lack of connectivity such as because the radio is disabled.
+ */
+ public static final int DISCOVERY_ERROR_NO_CONNECTIVITY = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = { CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+ CONNECTION_STATE_CONNECTED })
+ public @interface ConnectionState { }
+
+ /**
+ * Connection state: No destination has been selected. Media content should
+ * be sent to the default output.
+ */
+ public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+ /**
+ * Connection state: The application is in the process of connecting to
+ * a route offered by the selected destination.
+ */
+ public static final int CONNECTION_STATE_CONNECTING = 1;
+
+ /**
+ * Connection state: The application has connected to a route offered by
+ * the selected destination.
+ */
+ public static final int CONNECTION_STATE_CONNECTED = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = { CONNECTION_FLAG_BARGE })
+ public @interface ConnectionFlags { }
+
+ /**
+ * Connection flag: Indicates that the client has requested to barge in and evict
+ * other clients that might have already connected to the destination and that
+ * would otherwise prevent this client from connecting. When this flag is not
+ * set, the media route service should be polite and report
+ * {@link MediaRouter#CONNECTION_ERROR_BUSY} in case the destination is
+ * already occupied and cannot accept additional connections.
+ */
+ public static final int CONNECTION_FLAG_BARGE = 1 << 0;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = { CONNECTION_ERROR_UNKNOWN, CONNECTION_ERROR_ABORTED,
+ CONNECTION_ERROR_UNAUTHORIZED, CONNECTION_ERROR_UNAUTHORIZED,
+ CONNECTION_ERROR_BUSY, CONNECTION_ERROR_TIMEOUT, CONNECTION_ERROR_BROKEN })
+ public @interface ConnectionError { }
+
+ /**
+ * Connection error: Unknown error; refer to the error message for details.
+ */
+ public static final int CONNECTION_ERROR_UNKNOWN = 0;
+
+ /**
+ * Connection error: The media router or media route service has decided not to
+ * handle the connection request for some reason.
+ */
+ public static final int CONNECTION_ERROR_ABORTED = 1;
+
+ /**
+ * Connection error: The device has refused the connection from this client.
+ * This error should be avoided because the media route service should attempt
+ * to filter out devices that the client cannot access as it performs discovery
+ * on behalf of that client.
+ */
+ public static final int CONNECTION_ERROR_UNAUTHORIZED = 2;
+
+ /**
+ * Connection error: The device is unreachable over the network.
+ */
+ public static final int CONNECTION_ERROR_UNREACHABLE = 3;
+
+ /**
+ * Connection error: The device is already busy serving another client and
+ * the connection request did not ask to barge in.
+ */
+ public static final int CONNECTION_ERROR_BUSY = 4;
+
+ /**
+ * Connection error: A timeout occurred during connection.
+ */
+ public static final int CONNECTION_ERROR_TIMEOUT = 5;
+
+ /**
+ * Connection error: The connection to the device was severed unexpectedly.
+ */
+ public static final int CONNECTION_ERROR_BROKEN = 6;
+
+ /**
+ * Connection error: The connection was terminated because a different client barged
+ * in and took control of the destination.
+ */
+ public static final int CONNECTION_ERROR_BARGED = 7;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = { DISCONNECTION_REASON_APPLICATION_REQUEST,
+ DISCONNECTION_REASON_USER_REQUEST, DISCONNECTION_REASON_ERROR })
+ public @interface DisconnectionReason { }
+
+ /**
+ * Disconnection reason: The application requested disconnection itself.
+ */
+ public static final int DISCONNECTION_REASON_APPLICATION_REQUEST = 0;
+
+ /**
+ * Disconnection reason: The user requested disconnection.
+ */
+ public static final int DISCONNECTION_REASON_USER_REQUEST = 1;
+
+ /**
+ * Disconnection reason: An error occurred.
+ */
+ public static final int DISCONNECTION_REASON_ERROR = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = { ROUTE_FEATURE_LIVE_AUDIO, ROUTE_FEATURE_LIVE_VIDEO })
+ public @interface RouteFeatures { }
+
+ /**
+ * Route feature: Live audio.
+ * <p>
+ * A route that supports live audio streams audio rendered by the application
+ * to the destination.
+ * </p><p>
+ * To take advantage of live audio routing, the application must render its
+ * media using the audio attributes specified by {@link #getPreferredAudioAttributes}.
+ * </p>
+ *
+ * @see #getPreferredAudioAttributes
+ * @see android.media.AudioAttributes
+ */
+ public static final int ROUTE_FEATURE_LIVE_AUDIO = 1 << 0;
+
+ /**
+ * Route feature: Live video.
+ * <p>
+ * A route that supports live video streams video rendered by the application
+ * to the destination.
+ * </p><p>
+ * To take advantage of live video routing, the application must render its
+ * media to a {@link android.app.Presentation presentation window} on the
+ * display specified by {@link #getPreferredPresentationDisplay}.
+ * </p>
+ *
+ * @see #getPreferredPresentationDisplay
+ * @see android.app.Presentation
+ */
+ public static final int ROUTE_FEATURE_LIVE_VIDEO = 1 << 1;
+
+ /**
+ * Creates a media router.
+ *
+ * @param context The context with which the router is associated.
+ */
+ public MediaRouter(@NonNull Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+ }
+
+ /** @hide */
+ public IMediaRouter getBinder() {
+ // todo
+ return null;
+ }
+
+ /**
+ * Disconnects from the selected destination and releases the media router.
+ * <p>
+ * This method should be called by the application when it no longer requires
+ * the media router to ensure that all bound resources may be cleaned up.
+ * </p>
+ */
+ public void release() {
+ synchronized (mLock) {
+ mReleased = true;
+ // todo
+ }
+ }
+
+ /**
+ * Returns true if the media router has been released.
+ */
+ public boolean isReleased() {
+ synchronized (mLock) {
+ return mReleased;
+ }
+ }
+
+ /**
+ * Gets the current route discovery state.
+ *
+ * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+ * {@link #DISCOVERY_STATE_STARTED}.
+ */
+ public @DiscoveryState int getDiscoveryState() {
+ synchronized (mLock) {
+ return mDiscoveryState;
+ }
+ }
+
+ /**
+ * Gets the current route connection state.
+ *
+ * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ public @ConnectionState int getConnectionState() {
+ synchronized (mLock) {
+ return mConnectionState;
+ }
+ }
+
+ /**
+ * Creates a media router delegate through which the destination of the media
+ * router may be controlled.
+ * <p>
+ * This is the point of entry for UI code that initiates discovery and
+ * connection to routes.
+ * </p>
+ */
+ public @NonNull Delegate createDelegate() {
+ return null; // todo
+ }
+
+ /**
+ * Sets a callback to participate in route discovery, filtering, and connection
+ * establishment.
+ *
+ * @param callback The callback to set, or null if none.
+ * @param handler The handler to receive callbacks, or null to use the current thread.
+ */
+ public void setRoutingCallback(@Nullable RoutingCallback callback,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (callback == null) {
+ mRoutingCallback = null;
+ mRoutingCallbackHandler = null;
+ } else {
+ mRoutingCallback = callback;
+ mRoutingCallbackHandler = handler != null ? handler : new Handler();
+ }
+ }
+ }
+
+ /**
+ * Adds a media route selector to use to find destinations that have
+ * routes with the specified capabilities during route discovery.
+ */
+ public void addSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ synchronized (mLock) {
+ if (!mSelectors.contains(selector)) {
+ mSelectors.add(selector);
+ // todo
+ }
+ }
+ }
+
+ /**
+ * Removes a media route selector.
+ */
+ public void removeSelector(@NonNull MediaRouteSelector selector) {
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+
+ synchronized (mLock) {
+ if (mSelectors.remove(selector)) {
+ // todo
+ }
+ }
+ }
+
+ /**
+ * Removes all media route selectors.
+ * <p>
+ * Note that at least one selector must be added in order to perform discovery.
+ * </p>
+ */
+ public void clearSelectors() {
+ synchronized (mLock) {
+ if (!mSelectors.isEmpty()) {
+ mSelectors.clear();
+ // todo
+ }
+ }
+ }
+
+ /**
+ * Gets a list of all media route selectors to consider during discovery.
+ */
+ public @NonNull List<MediaRouteSelector> getSelectors() {
+ synchronized (mLock) {
+ return new ArrayList<MediaRouteSelector>(mSelectors);
+ }
+ }
+
+ /**
+ * Gets the connection to the currently selected route.
+ *
+ * @return The connection to the currently selected route, or null if not connected.
+ */
+ public @NonNull ConnectionInfo getConnection() {
+ synchronized (mLock) {
+ return mConnection;
+ }
+ }
+
+ /**
+ * Gets the list of discovered destinations.
+ * <p>
+ * This list is only valid while discovery is running and is null otherwise.
+ * </p>
+ *
+ * @return The list of discovered destinations, or null if discovery is not running.
+ */
+ public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+ synchronized (mLock) {
+ if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+ return new ArrayList<DestinationInfo>(mDiscoveredDestinations.keySet());
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Gets the list of discovered routes for a particular destination.
+ * <p>
+ * This list is only valid while discovery is running and is null otherwise.
+ * </p>
+ *
+ * @param destination The destination for which to get the list of discovered routes.
+ * @return The list of discovered routes for the destination, or null if discovery
+ * is not running.
+ */
+ public @NonNull List<RouteInfo> getDiscoveredRoutes(@NonNull DestinationInfo destination) {
+ if (destination == null) {
+ throw new IllegalArgumentException("destination must not be null");
+ }
+ synchronized (mLock) {
+ if (mDiscoveryState == DISCOVERY_STATE_STARTED) {
+ List<RouteInfo> routes = mDiscoveredDestinations.get(destination);
+ if (routes != null) {
+ return new ArrayList<RouteInfo>(routes);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Gets the destination that has been selected.
+ *
+ * @return The selected destination, or null if disconnected.
+ */
+ public @Nullable DestinationInfo getSelectedDestination() {
+ synchronized (mLock) {
+ return mSelectedRoute != null ? mSelectedRoute.getDestination() : null;
+ }
+ }
+
+ /**
+ * Gets the route that has been selected.
+ *
+ * @return The selected destination, or null if disconnected.
+ */
+ public @Nullable RouteInfo getSelectedRoute() {
+ synchronized (mLock) {
+ return mSelectedRoute;
+ }
+ }
+
+ /**
+ * Gets the preferred audio attributes that should be used to stream live audio content
+ * based on the connected route.
+ * <p>
+ * Use an {@link AudioTrack} to send audio content to the destination with these
+ * audio attributes.
+ * </p><p>
+ * The preferred audio attributes may change when a connection is established but it
+ * will remain constant until disconnected.
+ * </p>
+ *
+ * @return The preferred audio attributes to use. When connected, returns the
+ * route's audio attributes or null if it does not support live audio streaming.
+ * Otherwise returns audio attributes associated with {@link AudioAttributes#USAGE_MEDIA}.
+ */
+ public @Nullable AudioAttributes getPreferredAudioAttributes() {
+ synchronized (mLock) {
+ if (mConnection != null) {
+ return mConnection.getAudioAttributes();
+ }
+ return new AudioAttributes.Builder()
+ .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+ .build();
+ }
+ }
+
+ /**
+ * Gets the preferred presentation display that should be used to stream live video content
+ * based on the connected route.
+ * <p>
+ * Use a {@link Presentation} to send video content to the destination with this display.
+ * </p><p>
+ * The preferred presentation display may change when a connection is established but it
+ * will remain constant until disconnected.
+ * </p>
+ *
+ * @return The preferred presentation display to use. When connected, returns
+ * the route's presentation display or null if it does not support live video
+ * streaming. Otherwise returns the first available
+ * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION presentation display},
+ * such as a mirrored wireless or HDMI display or null if none.
+ */
+ public @Nullable Display getPreferredPresentationDisplay() {
+ synchronized (mLock) {
+ if (mConnection != null) {
+ return mConnection.getPresentationDisplay();
+ }
+ Display[] displays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
+ return displays.length != 0 ? displays[0] : null;
+ }
+ }
+
+ /**
+ * Gets the preferred volume provider that should be used to control the volume
+ * of content rendered on the currently selected route.
+ * <p>
+ * The preferred volume provider may change when a connection is established but it
+ * will remain the same until disconnected.
+ * </p>
+ *
+ * @return The preferred volume provider to use, or null if the currently
+ * selected route does not support remote volume adjustment or if the connection
+ * is not yet established. If no route is selected, returns null to indicate
+ * that system volume control should be used.
+ */
+ public @Nullable VolumeProvider getPreferredVolumeProvider() {
+ synchronized (mLock) {
+ if (mConnection != null) {
+ return mConnection.getVolumeProvider();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Requests to pause streaming of live audio or video routes.
+ * Should be called when the application is going into the background and is
+ * no longer rendering content locally.
+ * <p>
+ * This method does nothing unless a connection has been established.
+ * </p>
+ */
+ public void pauseStream() {
+ // todo
+ }
+
+ /**
+ * Requests to resume streaming of live audio or video routes.
+ * May be called when the application is returning to the foreground and is
+ * about to resume rendering content locally.
+ * <p>
+ * This method does nothing unless a connection has been established.
+ * </p>
+ */
+ public void resumeStream() {
+ // todo
+ }
+
+ /**
+ * This class is used by UI components to let the user discover and
+ * select a destination to which the media router should connect.
+ * <p>
+ * This API has somewhat more limited functionality than the {@link MediaRouter}
+ * itself because it is designed to allow applications to control
+ * the destination of media router instances that belong to other processes.
+ * </p><p>
+ * To control the destination of your own media router, call
+ * {@link #createDelegate} to obtain a local delegate object.
+ * </p><p>
+ * To control the destination of a media router that belongs to another process,
+ * first obtain a {@link MediaController} that is associated with the media playback
+ * that is occurring in that process, then call
+ * {@link MediaController#createMediaRouterDelegate} to obtain an instance of
+ * its destination controls. Note that special permissions may be required to
+ * obtain the {@link MediaController} instance in the first place.
+ * </p>
+ */
+ public static final class Delegate {
+ /**
+ * Returns true if the media router has been released.
+ */
+ public boolean isReleased() {
+ // todo
+ return false;
+ }
+
+ /**
+ * Gets the current route discovery state.
+ *
+ * @return The current discovery state: one of {@link #DISCOVERY_STATE_STOPPED},
+ * {@link #DISCOVERY_STATE_STARTED}.
+ */
+ public @DiscoveryState int getDiscoveryState() {
+ // todo
+ return -1;
+ }
+
+ /**
+ * Gets the current route connection state.
+ *
+ * @return The current state: one of {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING} or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ public @ConnectionState int getConnectionState() {
+ // todo
+ return -1;
+ }
+
+ /**
+ * Gets the currently selected destination.
+ *
+ * @return The destination information, or null if none.
+ */
+ public @Nullable DestinationInfo getSelectedDestination() {
+ return null;
+ }
+
+ /**
+ * Gets the list of discovered destinations.
+ * <p>
+ * This list is only valid while discovery is running and is null otherwise.
+ * </p>
+ *
+ * @return The list of discovered destinations, or null if discovery is not running.
+ */
+ public @NonNull List<DestinationInfo> getDiscoveredDestinations() {
+ return null;
+ }
+
+ /**
+ * Adds a callback to receive state changes.
+ *
+ * @param callback The callback to set, or null if none.
+ * @param handler The handler to receive callbacks, or null to use the current thread.
+ */
+ public void addStateCallback(@Nullable StateCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+ if (handler == null) {
+ handler = new Handler();
+ }
+ // todo
+ }
+
+ /**
+ * Removes a callback for state changes.
+ *
+ * @param callback The callback to set, or null if none.
+ */
+ public void removeStateCallback(@Nullable StateCallback callback) {
+ // todo
+ }
+
+ /**
+ * Starts performing discovery.
+ * <p>
+ * Performing discovery is expensive. Make sure to call {@link #stopDiscovery}
+ * as soon as possible once a new destination has been selected to allow the system
+ * to stop services associated with discovery.
+ * </p>
+ *
+ * @param flags The discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+ */
+ public void startDiscovery(@DiscoveryFlags int flags) {
+ // todo
+ }
+
+ /**
+ * Stops performing discovery.
+ */
+ public void stopDiscovery() {
+ // todo
+ }
+
+ /**
+ * Connects to a destination during route discovery.
+ * <p>
+ * This method may only be called while route discovery is active and the
+ * destination appears in the
+ * {@link #getDiscoveredDestinations list of discovered destinations}.
+ * If the media router is already connected to a route then it will first disconnect
+ * from the current route then connect to the new route.
+ * </p>
+ *
+ * @param destination The destination to which the media router should connect.
+ * @param flags The connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+ */
+ public void connect(@NonNull DestinationInfo destination, @DiscoveryFlags int flags) {
+ // todo
+ }
+
+ /**
+ * Disconnects from the currently selected destination.
+ * <p>
+ * Does nothing if not currently connected.
+ * </p>
+ *
+ * @param reason The reason for the disconnection: one of
+ * {@link #DISCONNECTION_REASON_APPLICATION_REQUEST},
+ * {@link #DISCONNECTION_REASON_USER_REQUEST}, or {@link #DISCONNECTION_REASON_ERROR}.
+ */
+ public void disconnect(@DisconnectionReason int reason) {
+ // todo
+ }
+ }
+
+ /**
+ * Describes immutable properties of a connection to a route.
+ */
+ public static final class ConnectionInfo {
+ private final RouteInfo mRoute;
+ private final AudioAttributes mAudioAttributes;
+ private final Display mPresentationDisplay;
+ private final VolumeProvider mVolumeProvider;
+ private final IBinder[] mProtocolBinders;
+ private final Object[] mProtocolInstances;
+ private final Bundle mExtras;
+ private final ArrayList<Closeable> mCloseables;
+
+ private static final Class<?>[] MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS =
+ new Class<?>[] { IBinder.class };
+
+ ConnectionInfo(RouteInfo route,
+ AudioAttributes audioAttributes, Display display,
+ VolumeProvider volumeProvider, IBinder[] protocolBinders,
+ Bundle extras, ArrayList<Closeable> closeables) {
+ mRoute = route;
+ mAudioAttributes = audioAttributes;
+ mPresentationDisplay = display;
+ mVolumeProvider = volumeProvider;
+ mProtocolBinders = protocolBinders;
+ mProtocolInstances = new Object[mProtocolBinders.length];
+ mExtras = extras;
+ mCloseables = closeables;
+ }
+
+ /**
+ * Gets the route that is connected.
+ */
+ public @NonNull RouteInfo getRoute() {
+ return mRoute;
+ }
+
+ /**
+ * Gets the audio attributes which the client should use to stream audio
+ * to the destination, or null if the route does not support live audio streaming.
+ */
+ public @Nullable AudioAttributes getAudioAttributes() {
+ return mAudioAttributes;
+ }
+
+ /**
+ * Gets the display which the client should use to stream video to the
+ * destination using a {@link Presentation}, or null if the route does not
+ * support live video streaming.
+ */
+ public @Nullable Display getPresentationDisplay() {
+ return mPresentationDisplay;
+ }
+
+ /**
+ * Gets the route's volume provider, or null if none.
+ */
+ public @Nullable VolumeProvider getVolumeProvider() {
+ return mVolumeProvider;
+ }
+
+ /**
+ * Gets the set of supported route features.
+ */
+ public @RouteFeatures int getFeatures() {
+ return mRoute.getFeatures();
+ }
+
+ /**
+ * Gets the list of supported route protocols.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @NonNull List<String> getProtocols() {
+ return mRoute.getProtocols();
+ }
+
+ /**
+ * Gets an instance of a route protocol object that wraps the protocol binder
+ * and provides easy access to the protocol's functionality.
+ * <p>
+ * This is a convenience method which invokes {@link #getProtocolBinder(String)}
+ * using the name of the provided class then passes the resulting {@link IBinder}
+ * to a single-argument constructor of that class.
+ * </p><p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ @SuppressWarnings("unchecked")
+ public @Nullable <T> T getProtocolObject(Class<T> clazz) {
+ int index = getProtocols().indexOf(clazz.getName());
+ if (index < 0) {
+ return null;
+ }
+ if (mProtocolInstances[index] == null && mProtocolBinders[index] != null) {
+ final Constructor<T> ctor;
+ try {
+ ctor = clazz.getConstructor(MEDIA_ROUTE_PROTOCOL_CTOR_PARAMETERS);
+ } catch (NoSuchMethodException ex) {
+ throw new RuntimeException("Could not find public constructor "
+ + "with IBinder argument in protocol class: " + clazz.getName(), ex);
+ }
+ try {
+ mProtocolInstances[index] = ctor.newInstance(mProtocolBinders[index]);
+ } catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException ex) {
+ throw new RuntimeException("Could create instance of protocol class: "
+ + clazz.getName(), ex);
+ }
+ }
+ return (T)mProtocolInstances[index];
+ }
+
+ /**
+ * Gets the {@link IBinder} that provides access to the specified route protocol
+ * or null if the protocol is not supported.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @Nullable IBinder getProtocolBinder(@NonNull String name) {
+ int index = getProtocols().indexOf(name);
+ return index >= 0 ? mProtocolBinders[index] : null;
+ }
+
+ /**
+ * Gets the {@link IBinder} that provides access to the specified route protocol
+ * at the given index in the protocol list or null if the protocol is not supported.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @Nullable IBinder getProtocolBinder(int index) {
+ return mProtocolBinders[index];
+ }
+
+ /**
+ * Gets optional extra media route service or protocol specific information about
+ * the connection. Use the service or protocol name as the prefix for
+ * any extras to avoid namespace collisions.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Closes all closeables associated with the connection when the connection
+ * is being torn down.
+ */
+ void close() {
+ final int count = mCloseables.size();
+ for (int i = 0; i < count; i++) {
+ try {
+ mCloseables.get(i).close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "ConnectionInfo{ route=" + mRoute
+ + ", audioAttributes=" + mAudioAttributes
+ + ", presentationDisplay=" + mPresentationDisplay
+ + ", volumeProvider=" + mVolumeProvider
+ + ", protocolBinders=" + mProtocolBinders + " }";
+ }
+
+ /**
+ * Builds {@link ConnectionInfo} objects.
+ */
+ public static final class Builder {
+ private final RouteInfo mRoute;
+ private AudioAttributes mAudioAttributes;
+ private Display mPresentationDisplay;
+ private VolumeProvider mVolumeProvider;
+ private final IBinder[] mProtocols;
+ private Bundle mExtras;
+ private final ArrayList<Closeable> mCloseables = new ArrayList<Closeable>();
+
+ /**
+ * Creates a builder for connection information.
+ *
+ * @param route The route that is connected.
+ */
+ public Builder(@NonNull RouteInfo route) {
+ if (route == null) {
+ throw new IllegalArgumentException("route");
+ }
+ mRoute = route;
+ mProtocols = new IBinder[route.getProtocols().size()];
+ }
+
+ /**
+ * Sets the audio attributes which the client should use to stream audio
+ * to the destination, or null if the route does not support live audio streaming.
+ */
+ public @NonNull Builder setAudioAttributes(
+ @Nullable AudioAttributes audioAttributes) {
+ mAudioAttributes = audioAttributes;
+ return this;
+ }
+
+ /**
+ * Sets the display which the client should use to stream video to the
+ * destination using a {@link Presentation}, or null if the route does not
+ * support live video streaming.
+ */
+ public @NonNull Builder setPresentationDisplay(@Nullable Display display) {
+ mPresentationDisplay = display;
+ return this;
+ }
+
+ /**
+ * Sets the route's volume provider, or null if none.
+ */
+ public @NonNull Builder setVolumeProvider(@Nullable VolumeProvider provider) {
+ mVolumeProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the binder stub of a supported route protocol using
+ * the protocol's fully qualified class name. The protocol must be one
+ * of those that was indicated as being supported by the route.
+ * <p>
+ * If the stub implements {@link Closeable} then it will automatically
+ * be closed when the client disconnects from the route.
+ * </p><p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @NonNull Builder setProtocolStub(@NonNull Class<?> clazz,
+ @NonNull IInterface stub) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("clazz must not be null");
+ }
+ if (stub == null) {
+ throw new IllegalArgumentException("stub must not be null");
+ }
+ if (stub instanceof Closeable) {
+ mCloseables.add((Closeable)stub);
+ }
+ return setProtocolBinder(clazz.getName(), stub.asBinder());
+ }
+
+ /**
+ * Sets the binder interface of a supported route protocol by name.
+ * The protocol must be one of those that was indicated as being supported
+ * by the route.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @NonNull Builder setProtocolBinder(@NonNull String name,
+ @NonNull IBinder binder) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null or empty");
+ }
+ if (binder == null) {
+ throw new IllegalArgumentException("binder must not be null");
+ }
+ int index = mRoute.getProtocols().indexOf(name);
+ if (index < 0) {
+ throw new IllegalArgumentException("name must specify a protocol that "
+ + "the route actually declared that it supports: "
+ + "name=" + name + ", protocols=" + mRoute.getProtocols());
+ }
+ mProtocols[index] = binder;
+ return this;
+ }
+
+ /**
+ * Sets optional extra media route service or protocol specific information about
+ * the connection. Use the service or protocol name as the prefix for
+ * any extras to avoid namespace collisions.
+ */
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ConnectionInfo} object.
+ */
+ public @NonNull ConnectionInfo build() {
+ return new ConnectionInfo(mRoute,
+ mAudioAttributes, mPresentationDisplay,
+ mVolumeProvider, mProtocols, mExtras, mCloseables);
+ }
+ }
+ }
+
+ /**
+ * Describes one particular way of routing media content to a destination
+ * according to the capabilities specified by a media route selector on behalf
+ * of an application.
+ */
+ public static final class RouteInfo {
+ private final String mId;
+ private final DestinationInfo mDestination;
+ private final MediaRouteSelector mSelector;
+ private final int mFeatures;
+ private final ArrayList<String> mProtocols;
+ private final Bundle mExtras;
+
+ RouteInfo(String id, DestinationInfo destination, MediaRouteSelector selector,
+ int features, ArrayList<String> protocols, Bundle extras) {
+ mId = id;
+ mDestination = destination;
+ mSelector = selector;
+ mFeatures = features;
+ mProtocols = protocols;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the route's stable identifier.
+ * <p>
+ * The id is intended to uniquely identify the route among all routes that
+ * are offered by a particular destination in such a way that the client can
+ * refer to it at a later time.
+ * </p>
+ */
+ public @NonNull String getId() {
+ return mId;
+ }
+
+ /**
+ * Gets the destination that is offering this route.
+ */
+ public @NonNull DestinationInfo getDestination() {
+ return mDestination;
+ }
+
+ /**
+ * Gets the media route selector provided by the client for which this
+ * route was created.
+ * <p>
+ * It is implied that this route supports all of the required capabilities
+ * that were expressed in the selector.
+ * </p>
+ */
+ public @NonNull MediaRouteSelector getSelector() {
+ return mSelector;
+ }
+
+ /**
+ * Gets the set of supported route features.
+ */
+ public @RouteFeatures int getFeatures() {
+ return mFeatures;
+ }
+
+ /**
+ * Gets the list of supported route protocols.
+ * <p>
+ * Refer to <code>android.support.media.protocols.MediaRouteProtocol</code>
+ * for more information.
+ * </p>
+ */
+ public @NonNull List<String> getProtocols() {
+ return mProtocols;
+ }
+
+ /**
+ * Gets optional extra information about the route, or null if none.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "RouteInfo{ id=" + mId + ", destination=" + mDestination
+ + ", features=0x" + Integer.toHexString(mFeatures)
+ + ", selector=" + mSelector + ", protocols=" + mProtocols
+ + ", extras=" + mExtras + " }";
+ }
+
+ /**
+ * Builds {@link RouteInfo} objects.
+ */
+ public static final class Builder {
+ private final DestinationInfo mDestination;
+ private final String mId;
+ private final MediaRouteSelector mSelector;
+ private int mFeatures;
+ private final ArrayList<String> mProtocols = new ArrayList<String>();
+ private Bundle mExtras;
+
+ /**
+ * Creates a builder for route information.
+ *
+ * @param id The route's stable identifier.
+ * @param destination The destination of this route.
+ * @param selector The media route selector provided by the client for which
+ * this route was created. This must be one of the selectors that was
+ * included in the discovery request.
+ */
+ public Builder(@NonNull String id, @NonNull DestinationInfo destination,
+ @NonNull MediaRouteSelector selector) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be null or empty");
+ }
+ if (destination == null) {
+ throw new IllegalArgumentException("destination must not be null");
+ }
+ if (selector == null) {
+ throw new IllegalArgumentException("selector must not be null");
+ }
+ mDestination = destination;
+ mId = id;
+ mSelector = selector;
+ }
+
+ /**
+ * Sets the set of supported route features.
+ */
+ public @NonNull Builder setFeatures(@RouteFeatures int features) {
+ mFeatures = features;
+ return this;
+ }
+
+ /**
+ * Adds a supported route protocol using its fully qualified class name.
+ * <p>
+ * If the protocol was not requested by the client in its selector
+ * then it will be silently discarded.
+ * </p>
+ */
+ public @NonNull <T extends IInterface> Builder addProtocol(@NonNull Class<T> clazz) {
+ if (clazz == null) {
+ throw new IllegalArgumentException("clazz must not be null");
+ }
+ return addProtocol(clazz.getName());
+ }
+
+ /**
+ * Adds a supported route protocol by name.
+ * <p>
+ * If the protocol was not requested by the client in its selector
+ * then it will be silently discarded.
+ * </p>
+ */
+ public @NonNull Builder addProtocol(@NonNull String name) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ if (mSelector.containsProtocol(name)) {
+ mProtocols.add(name);
+ }
+ return this;
+ }
+
+ /**
+ * Sets optional extra information about the route, or null if none.
+ */
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RouteInfo} object.
+ * <p>
+ * Ensures that all required protocols have been supplied.
+ * </p>
+ */
+ public @NonNull RouteInfo build() {
+ int missingFeatures = mSelector.getRequiredFeatures() & ~mFeatures;
+ if (missingFeatures != 0) {
+ throw new IllegalStateException("The media route selector "
+ + "specified required features which this route does "
+ + "not appear to support so it should not have been published: "
+ + "missing 0x" + Integer.toHexString(missingFeatures));
+ }
+ for (String protocol : mSelector.getRequiredProtocols()) {
+ if (!mProtocols.contains(protocol)) {
+ throw new IllegalStateException("The media route selector "
+ + "specified required protocols which this route "
+ + "does not appear to support so it should not have "
+ + "been published: missing " + protocol);
+ }
+ }
+ return new RouteInfo(mId, mDestination, mSelector,
+ mFeatures, mProtocols, mExtras);
+ }
+ }
+ }
+
+ /**
+ * Describes a destination for media content such as a device,
+ * an individual port on a device, or a group of devices.
+ */
+ public static final class DestinationInfo {
+ private final String mId;
+ private final ServiceMetadata mService;
+ private final CharSequence mName;
+ private final CharSequence mDescription;
+ private final int mIconResourceId;
+ private final Bundle mExtras;
+
+ DestinationInfo(String id, ServiceMetadata service,
+ CharSequence name, CharSequence description,
+ int iconResourceId, Bundle extras) {
+ mId = id;
+ mService = service;
+ mName = name;
+ mDescription = description;
+ mIconResourceId = iconResourceId;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the destination's stable identifier.
+ * <p>
+ * The id is intended to uniquely identify the destination among all destinations
+ * provided by the media route service in such a way that the client can
+ * refer to it at a later time. Ideally, the id should be resilient to
+ * user-initiated actions such as changes to the name or description
+ * of the destination.
+ * </p>
+ */
+ public @NonNull String getId() {
+ return mId;
+ }
+
+ /**
+ * Gets metadata about the service that is providing access to this destination.
+ */
+ public @NonNull ServiceMetadata getServiceMetadata() {
+ return mService;
+ }
+
+ /**
+ * Gets the destination's name for display to the user.
+ */
+ public @NonNull CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the destination's description for display to the user, or null if none.
+ */
+ public @Nullable CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets an icon resource from the service's package which is used
+ * to identify the destination, or -1 if none.
+ */
+ public @DrawableRes int getIconResourceId() {
+ return mIconResourceId;
+ }
+
+ /**
+ * Loads the icon drawable, or null if none.
+ */
+ public @Nullable Drawable loadIcon(@NonNull PackageManager pm) {
+ return mIconResourceId >= 0 ? mService.getDrawable(pm, mIconResourceId) : null;
+ }
+
+ /**
+ * Gets optional extra information about the destination, or null if none.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "DestinationInfo{ id=" + mId + ", service=" + mService + ", name=" + mName
+ + ", description=" + mDescription + ", iconResourceId=" + mIconResourceId
+ + ", extras=" + mExtras + " }";
+ }
+
+ /**
+ * Builds {@link DestinationInfo} objects.
+ */
+ public static final class Builder {
+ private final String mId;
+ private final ServiceMetadata mService;
+ private final CharSequence mName;
+ private CharSequence mDescription;
+ private int mIconResourceId = -1;
+ private Bundle mExtras;
+
+ /**
+ * Creates a builder for destination information.
+ *
+ * @param id The destination's stable identifier.
+ * @param service Metatada about the service that is providing access to
+ * this destination.
+ * @param name The destination's name for display to the user.
+ */
+ public Builder(@NonNull String id, @NonNull ServiceMetadata service,
+ @NonNull CharSequence name) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be null or empty");
+ }
+ if (service == null) {
+ throw new IllegalArgumentException("service must not be null");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be null or empty");
+ }
+ mId = id;
+ mService = service;
+ mName = name;
+ }
+
+ /**
+ * Sets the destination's description for display to the user, or null if none.
+ */
+ public @NonNull Builder setDescription(@Nullable CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Sets an icon resource from this package used to identify the destination,
+ * or -1 if none.
+ */
+ public @NonNull Builder setIconResourceId(@DrawableRes int resid) {
+ mIconResourceId = resid;
+ return this;
+ }
+
+ /**
+ * Gets optional extra information about the destination, or null if none.
+ */
+ public @NonNull Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link DestinationInfo} object.
+ */
+ public @NonNull DestinationInfo build() {
+ return new DestinationInfo(mId, mService, mName, mDescription,
+ mIconResourceId, mExtras);
+ }
+ }
+ }
+
+ /**
+ * Describes metadata about a {@link MediaRouteService} which is providing
+ * access to certain kinds of destinations.
+ */
+ public static final class ServiceMetadata {
+ private final ServiceInfo mService;
+ private CharSequence mLabel;
+ private Drawable mIcon;
+
+ ServiceMetadata(Service service) throws NameNotFoundException {
+ mService = service.getPackageManager().getServiceInfo(
+ new ComponentName(service, service.getClass()),
+ PackageManager.GET_META_DATA);
+ }
+
+ ServiceMetadata(ServiceInfo service) {
+ mService = service;
+ }
+
+ /**
+ * Gets the service's component information including it name, label and icon.
+ */
+ public @NonNull ServiceInfo getService() {
+ return mService;
+ }
+
+ /**
+ * Gets the service's component name.
+ */
+ public @NonNull ComponentName getComponentName() {
+ return new ComponentName(mService.packageName, mService.name);
+ }
+
+ /**
+ * Gets the service's package name.
+ */
+ public @NonNull String getPackageName() {
+ return mService.packageName;
+ }
+
+ /**
+ * Gets the service's name for display to the user, or null if none.
+ */
+ public @NonNull CharSequence getLabel(@NonNull PackageManager pm) {
+ if (mLabel == null) {
+ mLabel = mService.loadLabel(pm);
+ }
+ return mLabel;
+ }
+
+ /**
+ * Gets the icon drawable, or null if none.
+ */
+ public @Nullable Drawable getIcon(@NonNull PackageManager pm) {
+ if (mIcon == null) {
+ mIcon = mService.loadIcon(pm);
+ }
+ return mIcon;
+ }
+
+ // TODO: add service metadata
+
+ Drawable getDrawable(PackageManager pm, int resid) {
+ return pm.getDrawable(getPackageName(), resid, mService.applicationInfo);
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "ServiceInfo{ service=" + getComponentName().toShortString() + " }";
+ }
+ }
+
+ /**
+ * Describes a request to discover routes on behalf of an application.
+ */
+ public static final class DiscoveryRequest {
+ private final ArrayList<MediaRouteSelector> mSelectors =
+ new ArrayList<MediaRouteSelector>();
+ private int mFlags;
+
+ DiscoveryRequest(@NonNull List<MediaRouteSelector> selectors) {
+ setSelectors(selectors);
+ }
+
+ /**
+ * Sets the list of media route selectors to consider during discovery.
+ */
+ public void setSelectors(@NonNull List<MediaRouteSelector> selectors) {
+ if (selectors == null) {
+ throw new IllegalArgumentException("selectors");
+ }
+ mSelectors.clear();
+ mSelectors.addAll(selectors);
+ }
+
+ /**
+ * Gets the list of media route selectors to consider during discovery.
+ */
+ public @NonNull List<MediaRouteSelector> getSelectors() {
+ return mSelectors;
+ }
+
+ /**
+ * Gets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+ */
+ public @DiscoveryFlags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Sets discovery flags, such as {@link MediaRouter#DISCOVERY_FLAG_BACKGROUND}.
+ */
+ public void setFlags(@DiscoveryFlags int flags) {
+ mFlags = flags;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "DiscoveryRequest{ selectors=" + mSelectors
+ + ", flags=0x" + Integer.toHexString(mFlags)
+ + " }";
+ }
+ }
+
+ /**
+ * Describes a request to connect to a previously discovered route on
+ * behalf of an application.
+ */
+ public static final class ConnectionRequest {
+ private RouteInfo mRoute;
+ private int mFlags;
+ private Bundle mExtras;
+
+ ConnectionRequest(@NonNull RouteInfo route) {
+ setRoute(route);
+ }
+
+ /**
+ * Gets the route to which to connect.
+ */
+ public @NonNull RouteInfo getRoute() {
+ return mRoute;
+ }
+
+ /**
+ * Sets the route to which to connect.
+ */
+ public void setRoute(@NonNull RouteInfo route) {
+ if (route == null) {
+ throw new IllegalArgumentException("route must not be null");
+ }
+ mRoute = route;
+ }
+
+ /**
+ * Gets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+ */
+ public @ConnectionFlags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Sets connection flags, such as {@link MediaRouter#CONNECTION_FLAG_BARGE}.
+ */
+ public void setFlags(@ConnectionFlags int flags) {
+ mFlags = flags;
+ }
+
+ /**
+ * Gets optional extras supplied by the application as part of the call to
+ * connect, or null if none. The media route service may use this
+ * information to configure the route during connection.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Sets optional extras supplied by the application as part of the call to
+ * connect, or null if none. The media route service may use this
+ * information to configure the route during connection.
+ */
+ public void setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "ConnectionRequest{ route=" + mRoute
+ + ", flags=0x" + Integer.toHexString(mFlags)
+ + ", extras=" + mExtras + " }";
+ }
+ }
+
+ /**
+ * Callback interface to specify policy for route discovery, filtering,
+ * and connection establishment as well as observe media router state changes.
+ */
+ public static abstract class RoutingCallback extends StateCallback {
+ /**
+ * Called to prepare a discovery request object to specify the desired
+ * media route selectors when the media router has been asked to start discovery.
+ * <p>
+ * By default, the discovery request contains all of the selectors which
+ * have been added to the media router. Subclasses may override the list of
+ * selectors by modifying the discovery request object before returning.
+ * </p>
+ *
+ * @param request The discovery request object which may be modified by
+ * this method to alter how discovery will be performed.
+ * @param selectors The immutable list of media route selectors which were
+ * added to the media router.
+ * @return True to allow discovery to proceed or false to abort it.
+ * By default, this methods returns true.
+ */
+ public boolean onPrepareDiscoveryRequest(@NonNull DiscoveryRequest request,
+ @NonNull List<MediaRouteSelector> selectors) {
+ return true;
+ }
+
+ /**
+ * Called to prepare a connection request object to specify the desired
+ * route and connection parameters when the media router has been asked to
+ * connect to a particular destination.
+ * <p>
+ * By default, the connection request specifies the first available route
+ * to the destination. Subclasses may override the route and destination
+ * or set additional connection parameters by modifying the connection request
+ * object before returning.
+ * </p>
+ *
+ * @param request The connection request object which may be modified by
+ * this method to alter how the connection will be established.
+ * @param destination The destination to which the media router was asked
+ * to connect.
+ * @param routes The list of routes that belong to that destination sorted
+ * in the same order as their matching media route selectors which were
+ * used during discovery.
+ * @return True to allow the connection to proceed or false to abort it.
+ * By default, this methods returns true.
+ */
+ public boolean onPrepareConnectionRequest(
+ @NonNull ConnectionRequest request,
+ @NonNull DestinationInfo destination, @NonNull List<RouteInfo> routes) {
+ return true;
+ }
+ }
+
+ /**
+ * Callback class to receive events from a {@link MediaRouter.Delegate}.
+ */
+ public static abstract class StateCallback {
+ /**
+ * Called when the media router has been released.
+ */
+ public void onReleased() { }
+
+ /**
+ * Called when the discovery state has changed.
+ *
+ * @param state The new discovery state: one of
+ * {@link #DISCOVERY_STATE_STOPPED} or {@link #DISCOVERY_STATE_STARTED}.
+ */
+ public void onDiscoveryStateChanged(@DiscoveryState int state) { }
+
+ /**
+ * Called when the connection state has changed.
+ *
+ * @param state The new connection state: one of
+ * {@link #CONNECTION_STATE_DISCONNECTED}, {@link #CONNECTION_STATE_CONNECTING}
+ * or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ public void onConnectionStateChanged(@ConnectionState int state) { }
+
+ /**
+ * Called when the selected destination has changed.
+ *
+ * @param destination The new selected destination, or null if none.
+ */
+ public void onSelectedDestinationChanged(@Nullable DestinationInfo destination) { }
+
+ /**
+ * Called when route discovery has started.
+ */
+ public void onDiscoveryStarted() { }
+
+ /**
+ * Called when route discovery has stopped normally.
+ * <p>
+ * Abnormal termination is reported via {@link #onDiscoveryFailed}.
+ * </p>
+ */
+ public void onDiscoveryStopped() { }
+
+ /**
+ * Called when discovery has failed in a non-recoverable manner.
+ *
+ * @param error The error code: one of
+ * {@link MediaRouter#DISCOVERY_ERROR_UNKNOWN},
+ * {@link MediaRouter#DISCOVERY_ERROR_ABORTED},
+ * or {@link MediaRouter#DISCOVERY_ERROR_NO_CONNECTIVITY}.
+ * @param message The localized error message, or null if none. This message
+ * may be shown to the user.
+ * @param extras Additional information about the error which a client
+ * may use, or null if none.
+ */
+ public void onDiscoveryFailed(@DiscoveryError int error, @Nullable CharSequence message,
+ @Nullable Bundle extras) { }
+
+ /**
+ * Called when a new destination is found or has changed during discovery.
+ * <p>
+ * Certain destinations may be omitted because they have been filtered
+ * out by the media router's routing callback.
+ * </p>
+ *
+ * @param destination The destination that was found.
+ */
+ public void onDestinationFound(@NonNull DestinationInfo destination) { }
+
+ /**
+ * Called when a destination is no longer reachable or is no longer
+ * offering any routes that satisfy the discovery request.
+ *
+ * @param destination The destination that went away.
+ */
+ public void onDestinationLost(@NonNull DestinationInfo destination) { }
+
+ /**
+ * Called when a connection attempt begins.
+ */
+ public void onConnecting() { }
+
+ /**
+ * Called when the connection succeeds.
+ */
+ public void onConnected() { }
+
+ /**
+ * Called when the connection is terminated normally.
+ * <p>
+ * Abnormal termination is reported via {@link #onConnectionFailed}.
+ * </p>
+ */
+ public void onDisconnected() { }
+
+ /**
+ * Called when a connection attempt or connection in
+ * progress has failed in a non-recoverable manner.
+ *
+ * @param error The error code: one of
+ * {@link MediaRouter#CONNECTION_ERROR_ABORTED},
+ * {@link MediaRouter#CONNECTION_ERROR_UNAUTHORIZED},
+ * {@link MediaRouter#CONNECTION_ERROR_UNREACHABLE},
+ * {@link MediaRouter#CONNECTION_ERROR_BUSY},
+ * {@link MediaRouter#CONNECTION_ERROR_TIMEOUT},
+ * {@link MediaRouter#CONNECTION_ERROR_BROKEN},
+ * or {@link MediaRouter#CONNECTION_ERROR_BARGED}.
+ * @param message The localized error message, or null if none. This message
+ * may be shown to the user.
+ * @param extras Additional information about the error which a client
+ * may use, or null if none.
+ */
+ public void onConnectionFailed(@ConnectionError int error,
+ @Nullable CharSequence message, @Nullable Bundle extras) { }
+ }
+}
diff --git a/media/java/android/media/session/RouteEvent.aidl b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
index 6966207..4a9ec94 100644
--- a/media/java/android/media/session/RouteEvent.aidl
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
@@ -13,6 +13,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.media.routing;
-parcelable RouteEvent;
+parcelable ParcelableConnectionInfo;
diff --git a/media/java/android/media/routing/ParcelableConnectionInfo.java b/media/java/android/media/routing/ParcelableConnectionInfo.java
new file mode 100644
index 0000000..45cfe9f
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableConnectionInfo.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.media.AudioAttributes;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route connection.
+ */
+class ParcelableConnectionInfo implements Parcelable {
+ public AudioAttributes audioAttributes;
+ public int presentationDisplayId = -1;
+ // todo: volume
+ public IBinder[] protocolBinders;
+ public Bundle extras;
+
+ public static final Parcelable.Creator<ParcelableConnectionInfo> CREATOR =
+ new Parcelable.Creator<ParcelableConnectionInfo>() {
+ @Override
+ public ParcelableConnectionInfo createFromParcel(Parcel source) {
+ ParcelableConnectionInfo info = new ParcelableConnectionInfo();
+ if (source.readInt() != 0) {
+ info.audioAttributes = AudioAttributes.CREATOR.createFromParcel(source);
+ }
+ info.presentationDisplayId = source.readInt();
+ info.protocolBinders = source.createBinderArray();
+ info.extras = source.readBundle();
+ return info;
+ }
+
+ @Override
+ public ParcelableConnectionInfo[] newArray(int size) {
+ return new ParcelableConnectionInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (audioAttributes != null) {
+ dest.writeInt(1);
+ audioAttributes.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(presentationDisplayId);
+ dest.writeBinderArray(protocolBinders);
+ dest.writeBundle(extras);
+ }
+}
diff --git a/media/java/android/media/session/RouteCommand.aidl b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
index 725b308..bf1c198 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.aidl
@@ -13,6 +13,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.media.routing;
-parcelable RouteCommand;
+parcelable ParcelableDestinationInfo;
diff --git a/media/java/android/media/routing/ParcelableDestinationInfo.java b/media/java/android/media/routing/ParcelableDestinationInfo.java
new file mode 100644
index 0000000..eca5eec
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableDestinationInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Internal parcelable representation of a media destination.
+ */
+class ParcelableDestinationInfo implements Parcelable {
+ public String id;
+ public CharSequence name;
+ public CharSequence description;
+ public int iconResourceId;
+ public Bundle extras;
+
+ public static final Parcelable.Creator<ParcelableDestinationInfo> CREATOR =
+ new Parcelable.Creator<ParcelableDestinationInfo>() {
+ @Override
+ public ParcelableDestinationInfo createFromParcel(Parcel source) {
+ ParcelableDestinationInfo info = new ParcelableDestinationInfo();
+ info.id = source.readString();
+ info.name = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ info.description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ info.iconResourceId = source.readInt();
+ info.extras = source.readBundle();
+ return info;
+ }
+
+ @Override
+ public ParcelableDestinationInfo[] newArray(int size) {
+ return new ParcelableDestinationInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ TextUtils.writeToParcel(name, dest, flags);
+ TextUtils.writeToParcel(description, dest, flags);
+ dest.writeInt(iconResourceId);
+ dest.writeBundle(extras);
+ }
+}
diff --git a/media/java/android/media/routeprovider/RouteRequest.aidl b/media/java/android/media/routing/ParcelableRouteInfo.aidl
index 7bc5722..126afaa 100644
--- a/media/java/android/media/routeprovider/RouteRequest.aidl
+++ b/media/java/android/media/routing/ParcelableRouteInfo.aidl
@@ -13,6 +13,6 @@
** limitations under the License.
*/
-package android.media.routeprovider;
+package android.media.routing;
-parcelable RouteRequest;
+parcelable ParcelableRouteInfo;
diff --git a/media/java/android/media/routing/ParcelableRouteInfo.java b/media/java/android/media/routing/ParcelableRouteInfo.java
new file mode 100644
index 0000000..fb1a547
--- /dev/null
+++ b/media/java/android/media/routing/ParcelableRouteInfo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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.routing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Internal parcelable representation of a media route.
+ */
+class ParcelableRouteInfo implements Parcelable {
+ public String id;
+ public int selectorIndex; // index of selector within list used for discovery
+ public int features;
+ public String[] protocols;
+ public Bundle extras;
+
+ public static final Parcelable.Creator<ParcelableRouteInfo> CREATOR =
+ new Parcelable.Creator<ParcelableRouteInfo>() {
+ @Override
+ public ParcelableRouteInfo createFromParcel(Parcel source) {
+ ParcelableRouteInfo info = new ParcelableRouteInfo();
+ info.id = source.readString();
+ info.selectorIndex = source.readInt();
+ info.features = source.readInt();
+ info.protocols = source.createStringArray();
+ info.extras = source.readBundle();
+ return info;
+ }
+
+ @Override
+ public ParcelableRouteInfo[] newArray(int size) {
+ return new ParcelableRouteInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeInt(selectorIndex);
+ dest.writeInt(features);
+ dest.writeStringArray(protocols);
+ dest.writeBundle(extras);
+ }
+}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 5bc0de4..a92350b 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -17,10 +17,8 @@ package android.media.session;
import android.content.ComponentName;
import android.media.MediaMetadata;
+import android.media.routing.IMediaRouter;
import android.media.session.ISessionController;
-import android.media.session.RouteOptions;
-import android.media.session.RouteCommand;
-import android.media.session.RouteInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -34,17 +32,10 @@ interface ISession {
ISessionController getController();
void setFlags(int flags);
void setActive(boolean active);
+ void setMediaRouter(in IMediaRouter router);
void setMediaButtonReceiver(in ComponentName mbr);
void destroy();
- // These commands are for setting up and communicating with routes
- // Returns true if the route was set for this session
- boolean setRoute(in RouteInfo route);
- void setRouteOptions(in List<RouteOptions> options);
- void connectToRoute(in RouteInfo route, in RouteOptions options);
- void disconnectFromRoute(in RouteInfo route);
- void sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
-
// These commands are for the TransportPerformer
void setMetadata(in MediaMetadata metadata);
void setPlaybackState(in PlaybackState state);
@@ -53,4 +44,4 @@ interface ISession {
// These commands relate to volume handling
void configureVolumeHandling(int type, int arg1, int arg2);
void setCurrentVolume(int currentVolume);
-} \ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 0316d1f..e554e27 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -16,9 +16,6 @@
package android.media.session;
import android.media.Rating;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -29,11 +26,6 @@ import android.os.ResultReceiver;
oneway interface ISessionCallback {
void onCommand(String command, in Bundle extras, in ResultReceiver cb);
void onMediaButton(in Intent mediaButtonIntent, int sequenceNumber, in ResultReceiver cb);
- void onRequestRouteChange(in RouteInfo route);
- void onRouteConnected(in RouteInfo route, in RouteOptions options);
- void onRouteDisconnected(in RouteInfo route, int reason);
- void onRouteStateChange(int state);
- void onRouteEvent(in RouteEvent event);
// These callbacks are for the TransportPerformer
void onPlay();
@@ -49,4 +41,4 @@ oneway interface ISessionCallback {
// These callbacks are for volume handling
void onAdjustVolumeBy(int delta);
void onSetVolumeTo(int value);
-} \ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index b4c11f6..6cf5ef2 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -18,6 +18,8 @@ package android.media.session;
import android.content.Intent;
import android.media.MediaMetadata;
import android.media.Rating;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
import android.media.session.ISessionControllerCallback;
import android.media.session.MediaSessionInfo;
import android.media.session.ParcelableVolumeInfo;
@@ -36,14 +38,15 @@ interface ISessionController {
void registerCallbackListener(in ISessionControllerCallback cb);
void unregisterCallbackListener(in ISessionControllerCallback cb);
boolean isTransportControlEnabled();
- void showRoutePicker();
MediaSessionInfo getSessionInfo();
long getFlags();
ParcelableVolumeInfo getVolumeAttributes();
void adjustVolumeBy(int delta, int flags);
void setVolumeTo(int value, int flags);
- // These commands are for the TransportController
+ IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
+
+ // These commands are for the TransportControls
void play();
void pause();
void stop();
@@ -56,4 +59,4 @@ interface ISessionController {
MediaMetadata getMetadata();
PlaybackState getPlaybackState();
int getRatingType();
-} \ No newline at end of file
+}
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index baa1379..64d2bc7 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,7 +16,6 @@
package android.media.session;
import android.media.MediaMetadata;
-import android.media.session.RouteInfo;
import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -26,10 +25,9 @@ import android.os.Bundle;
*/
oneway interface ISessionControllerCallback {
void onEvent(String event, in Bundle extras);
- void onRouteChanged(in RouteInfo route);
// These callbacks are for the TransportController
void onPlaybackStateChanged(in PlaybackState state);
void onMetadataChanged(in MediaMetadata metadata);
void onVolumeInfoChanged(in ParcelableVolumeInfo info);
-} \ No newline at end of file
+}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index edb69bc..cc8b31a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -22,6 +22,7 @@ import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -52,8 +53,7 @@ public final class MediaController {
private static final int MSG_EVENT = 1;
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
private static final int MSG_UPDATE_METADATA = 3;
- private static final int MSG_ROUTE = 4;
- private static final int MSG_UPDATE_VOLUME = 5;
+ private static final int MSG_UPDATE_VOLUME = 4;
private final ISessionController mSessionBinder;
@@ -64,11 +64,11 @@ public final class MediaController {
private boolean mCbRegistered = false;
private MediaSessionInfo mInfo;
- private TransportControls mTransportController;
+ private final TransportControls mTransportControls;
private MediaController(ISessionController sessionBinder) {
mSessionBinder = sessionBinder;
- mTransportController = new TransportControls();
+ mTransportControls = new TransportControls();
}
/**
@@ -91,12 +91,24 @@ public final class MediaController {
}
/**
- * Get a {@link TransportControls} instance for this session.
+ * Get a {@link TransportControls} instance to send transport actions to
+ * the associated session.
*
- * @return A controls instance
+ * @return A transport controls instance.
*/
public @NonNull TransportControls getTransportControls() {
- return mTransportController;
+ return mTransportControls;
+ }
+
+ /**
+ * Creates a media router delegate through which the destination of the media
+ * router may be observed and controlled.
+ *
+ * @return The media router delegate, or null if the media session does
+ * not support media routing.
+ */
+ public @Nullable MediaRouter.Delegate createMediaRouterDelegate() {
+ return new MediaRouter.Delegate();
}
/**
@@ -308,20 +320,6 @@ public final class MediaController {
}
/**
- * Request that the route picker be shown for this session. This should
- * generally be called in response to a user action.
- *
- * @hide
- */
- public void showRoutePicker() {
- try {
- mSessionBinder.showRoutePicker();
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in showRoutePicker", e);
- }
- }
-
- /**
* Get the info for the session this controller is connected to.
*
* @return The session info for the connected session.
@@ -421,15 +419,6 @@ public final class MediaController {
}
/**
- * Override to handle route changes for this session.
- *
- * @param route The new route
- * @hide
- */
- public void onRouteChanged(RouteInfo route) {
- }
-
- /**
* Override to handle changes in playback state.
*
* @param state The new playback state of the session
@@ -670,14 +659,6 @@ public final class MediaController {
}
@Override
- public void onRouteChanged(RouteInfo route) {
- MediaController controller = mController.get();
- if (controller != null) {
- controller.postMessage(MSG_ROUTE, route, null);
- }
- }
-
- @Override
public void onPlaybackStateChanged(PlaybackState state) {
MediaController controller = mController.get();
if (controller != null) {
@@ -719,9 +700,6 @@ public final class MediaController {
case MSG_EVENT:
mCallback.onSessionEvent((String) msg.obj, msg.getData());
break;
- case MSG_ROUTE:
- mCallback.onRouteChanged((RouteInfo) msg.obj);
- break;
case MSG_UPDATE_PLAYBACK_STATE:
mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
break;
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 2cbdc96..34997bd 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -25,6 +25,7 @@ import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.routing.MediaRouter;
import android.media.session.ISessionController;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -93,40 +94,6 @@ public final class MediaSession {
public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
/**
- * Indicates the session was disconnected because the user that the session
- * belonged to is stopping.
- *
- * @hide
- */
- public static final int DISCONNECT_REASON_USER_STOPPING = 1;
-
- /**
- * Indicates the session was disconnected because the provider disconnected
- * the route.
- * @hide
- */
- public static final int DISCONNECT_REASON_PROVIDER_DISCONNECTED = 2;
-
- /**
- * Indicates the session was disconnected because the route has changed.
- * @hide
- */
- public static final int DISCONNECT_REASON_ROUTE_CHANGED = 3;
-
- /**
- * Indicates the session was disconnected because the session owner
- * requested it disconnect.
- * @hide
- */
- public static final int DISCONNECT_REASON_SESSION_DISCONNECTED = 4;
-
- /**
- * Indicates the session was disconnected because it was destroyed.
- * @hide
- */
- public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
-
- /**
* The session uses local playback.
*/
public static final int PLAYBACK_TYPE_LOCAL = 1;
@@ -146,11 +113,7 @@ public final class MediaSession {
= new ArrayList<CallbackMessageHandler>();
private final ArrayList<TransportMessageHandler> mTransportCallbacks
= new ArrayList<TransportMessageHandler>();
- // TODO route interfaces
- private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
- = new ArrayMap<String, RouteInterface.EventListener>();
- private Route mRoute;
private VolumeProvider mVolumeProvider;
private boolean mActive = false;
@@ -228,6 +191,23 @@ public final class MediaSession {
}
/**
+ * Associates a {@link MediaRouter} with this session to control the destination
+ * of media content.
+ * <p>
+ * A media router may only be associated with at most one session at a time.
+ * </p>
+ *
+ * @param router The media router, or null to remove the current association.
+ */
+ public void setMediaRouter(@Nullable MediaRouter router) {
+ try {
+ mBinder.setMediaRouter(router != null ? router.getBinder() : null);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
+ }
+ }
+
+ /**
* Set a media button event receiver component to use to restart playback
* after an app has been stopped.
*
@@ -377,90 +357,6 @@ public final class MediaSession {
}
/**
- * Connect to the current route using the specified request.
- * <p>
- * Connection updates will be sent to the callback's
- * {@link Callback#onRouteConnected(Route)} and
- * {@link Callback#onRouteDisconnected(Route, int)} methods. If the
- * connection fails {@link Callback#onRouteDisconnected(Route, int)} will be
- * called.
- * <p>
- * If you already have a connection to this route it will be disconnected
- * before the new connection is established. TODO add an easy way to compare
- * MediaRouteOptions.
- *
- * @param route The route the app is trying to connect to.
- * @param request The connection request to use.
- * @hide
- */
- public void connect(RouteInfo route, RouteOptions request) {
- if (route == null) {
- throw new IllegalArgumentException("Must specify the route");
- }
- if (request == null) {
- throw new IllegalArgumentException("Must specify the connection request");
- }
- try {
- mBinder.connectToRoute(route, request);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error starting connection to route", e);
- }
- }
-
- /**
- * Disconnect from the current route. After calling you will be switched
- * back to the default route.
- *
- * @hide
- */
- public void disconnect() {
- if (mRoute != null) {
- try {
- mBinder.disconnectFromRoute(mRoute.getRouteInfo());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error disconnecting from route");
- }
- }
- }
-
- /**
- * Set the list of route options your app is interested in connecting to. It
- * will be used for picking valid routes.
- *
- * @param options The set of route options your app may use to connect.
- * @hide
- */
- public void setRouteOptions(List<RouteOptions> options) {
- try {
- mBinder.setRouteOptions(options);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error setting route options.", e);
- }
- }
-
- /**
- * @hide
- * TODO allow multiple listeners for the same interface, allow removal
- */
- public void addInterfaceListener(String iface,
- RouteInterface.EventListener listener) {
- mInterfaceListeners.put(iface, listener);
- }
-
- /**
- * @hide
- */
- public boolean sendRouteCommand(RouteCommand command, ResultReceiver cb) {
- try {
- mBinder.sendRouteCommand(command, cb);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error sending command to route.", e);
- return false;
- }
- return true;
- }
-
- /**
* Add a callback to receive transport controls on, such as play, rewind, or
* fast forward.
*
@@ -670,34 +566,6 @@ public final class MediaSession {
}
}
- private void postRequestRouteChange(RouteInfo route) {
- synchronized (mLock) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CHANGE, route);
- }
- }
- }
-
- private void postRouteConnected(RouteInfo route, RouteOptions options) {
- synchronized (mLock) {
- mRoute = new Route(route, options, this);
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_CONNECTED, mRoute);
- }
- }
- }
-
- private void postRouteDisconnected(RouteInfo route, int reason) {
- synchronized (mLock) {
- if (mRoute != null && TextUtils.equals(mRoute.getRouteInfo().getId(), route.getId())) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(CallbackMessageHandler.MSG_ROUTE_DISCONNECTED, mRoute,
- reason);
- }
- }
- }
- }
-
/**
* Return true if this is considered an active playback state.
*
@@ -796,47 +664,6 @@ public final class MediaSession {
public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
@Nullable ResultReceiver cb) {
}
-
- /**
- * Called when the user has selected a different route to connect to.
- * The app is responsible for connecting to the new route and migrating
- * ongoing playback if necessary.
- *
- * @param route
- * @hide
- */
- public void onRequestRouteChange(RouteInfo route) {
- }
-
- /**
- * Called when a route has successfully connected. Calls to the route
- * are now valid.
- *
- * @param route The route that was connected
- * @hide
- */
- public void onRouteConnected(Route route) {
- }
-
- /**
- * Called when a route was disconnected. Further calls to the route will
- * fail. If available a reason for being disconnected will be provided.
- * <p>
- * Valid reasons are:
- * <ul>
- * <li>{@link #DISCONNECT_REASON_USER_STOPPING}</li>
- * <li>{@link #DISCONNECT_REASON_PROVIDER_DISCONNECTED}</li>
- * <li>{@link #DISCONNECT_REASON_ROUTE_CHANGED}</li>
- * <li>{@link #DISCONNECT_REASON_SESSION_DISCONNECTED}</li>
- * <li>{@link #DISCONNECT_REASON_SESSION_DESTROYED}</li>
- * </ul>
- *
- * @param route The route that disconnected
- * @param reason The reason for the disconnect
- * @hide
- */
- public void onRouteDisconnected(Route route, int reason) {
- }
}
/**
@@ -902,17 +729,6 @@ public final class MediaSession {
*/
public void onSetRating(@NonNull Rating rating) {
}
-
- /**
- * Report that audio focus has changed on the app. This only happens if
- * you have indicated you have started playing with
- * {@link #setPlaybackState}.
- *
- * @param focusChange The type of focus change, TBD.
- * @hide
- */
- public void onRouteFocusChange(int focusChange) {
- }
}
/**
@@ -926,8 +742,7 @@ public final class MediaSession {
}
@Override
- public void onCommand(String command, Bundle extras, ResultReceiver cb)
- throws RemoteException {
+ public void onCommand(String command, Bundle extras, ResultReceiver cb) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.postCommand(command, extras, cb);
@@ -935,8 +750,8 @@ public final class MediaSession {
}
@Override
- public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)
- throws RemoteException {
+ public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
+ ResultReceiver cb) {
MediaSession session = mMediaSession.get();
try {
if (session != null) {
@@ -950,31 +765,7 @@ public final class MediaSession {
}
@Override
- public void onRequestRouteChange(RouteInfo route) throws RemoteException {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.postRequestRouteChange(route);
- }
- }
-
- @Override
- public void onRouteConnected(RouteInfo route, RouteOptions options) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.postRouteConnected(route, options);
- }
- }
-
- @Override
- public void onRouteDisconnected(RouteInfo route, int reason) {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- session.postRouteDisconnected(route, reason);
- }
- }
-
- @Override
- public void onPlay() throws RemoteException {
+ public void onPlay() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPlay();
@@ -982,7 +773,7 @@ public final class MediaSession {
}
@Override
- public void onPause() throws RemoteException {
+ public void onPause() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPause();
@@ -990,7 +781,7 @@ public final class MediaSession {
}
@Override
- public void onStop() throws RemoteException {
+ public void onStop() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchStop();
@@ -998,7 +789,7 @@ public final class MediaSession {
}
@Override
- public void onNext() throws RemoteException {
+ public void onNext() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchNext();
@@ -1006,7 +797,7 @@ public final class MediaSession {
}
@Override
- public void onPrevious() throws RemoteException {
+ public void onPrevious() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrevious();
@@ -1014,7 +805,7 @@ public final class MediaSession {
}
@Override
- public void onFastForward() throws RemoteException {
+ public void onFastForward() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchFastForward();
@@ -1022,7 +813,7 @@ public final class MediaSession {
}
@Override
- public void onRewind() throws RemoteException {
+ public void onRewind() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchRewind();
@@ -1030,7 +821,7 @@ public final class MediaSession {
}
@Override
- public void onSeekTo(long pos) throws RemoteException {
+ public void onSeekTo(long pos) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchSeekTo(pos);
@@ -1038,7 +829,7 @@ public final class MediaSession {
}
@Override
- public void onRate(Rating rating) throws RemoteException {
+ public void onRate(Rating rating) {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchRate(rating);
@@ -1046,26 +837,7 @@ public final class MediaSession {
}
@Override
- public void onRouteEvent(RouteEvent event) throws RemoteException {
- MediaSession session = mMediaSession.get();
- if (session != null) {
- RouteInterface.EventListener iface
- = session.mInterfaceListeners.get(event.getIface());
- Log.d(TAG, "Received route event on iface " + event.getIface() + ". Listener is "
- + iface);
- if (iface != null) {
- iface.onEvent(event.getEvent(), event.getExtras());
- }
- }
- }
-
- @Override
- public void onRouteStateChange(int state) throws RemoteException {
- // TODO
- }
-
- @Override
- public void onAdjustVolumeBy(int delta) throws RemoteException {
+ public void onAdjustVolumeBy(int delta) {
MediaSession session = mMediaSession.get();
if (session != null) {
if (session.mVolumeProvider != null) {
@@ -1075,7 +847,7 @@ public final class MediaSession {
}
@Override
- public void onSetVolumeTo(int value) throws RemoteException {
+ public void onSetVolumeTo(int value) {
MediaSession session = mMediaSession.get();
if (session != null) {
if (session.mVolumeProvider != null) {
@@ -1089,9 +861,6 @@ public final class MediaSession {
private class CallbackMessageHandler extends Handler {
private static final int MSG_MEDIA_BUTTON = 1;
private static final int MSG_COMMAND = 2;
- private static final int MSG_ROUTE_CHANGE = 3;
- private static final int MSG_ROUTE_CONNECTED = 4;
- private static final int MSG_ROUTE_DISCONNECTED = 5;
private MediaSession.Callback mCallback;
@@ -1114,15 +883,6 @@ public final class MediaSession {
Command cmd = (Command) msg.obj;
mCallback.onControlCommand(cmd.command, cmd.extras, cmd.stub);
break;
- case MSG_ROUTE_CHANGE:
- mCallback.onRequestRouteChange((RouteInfo) msg.obj);
- break;
- case MSG_ROUTE_CONNECTED:
- mCallback.onRouteConnected((Route) msg.obj);
- break;
- case MSG_ROUTE_DISCONNECTED:
- mCallback.onRouteDisconnected((Route) msg.obj, msg.arg1);
- break;
}
}
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c73a8d3..c477406 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -35,9 +35,8 @@ import java.util.ArrayList;
import java.util.List;
/**
- * MediaSessionManager allows the creation and control of MediaSessions in the
- * system. A MediaSession enables publishing information about ongoing media and
- * interacting with MediaControllers and MediaRoutes.
+ * Provides support for interacting with {@link MediaSession media sessions}
+ * that applications have published to express their ongoing media playback state.
* <p>
* Use <code>Context.getSystemService(Context.MEDIA_SESSION_SERVICE)</code> to
* get an instance of this class.
@@ -256,8 +255,8 @@ public final class MediaSessionManager {
}
/**
- * Dispatch an adjust volume request to the system. It will be routed to the
- * most relevant stream/session.
+ * Dispatch an adjust volume request to the system. It will be sent to the
+ * most relevant audio stream or media session.
*
* @param suggestedStream The stream to fall back to if there isn't a
* relevant stream
@@ -292,8 +291,7 @@ public final class MediaSessionManager {
private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
@Override
- public void onActiveSessionsChanged(List<MediaSession.Token> tokens)
- throws RemoteException {
+ public void onActiveSessionsChanged(List<MediaSession.Token> tokens) {
ArrayList<MediaController> controllers = new ArrayList<MediaController>();
int size = tokens.size();
for (int i = 0; i < size; i++) {
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 6125cb4..9ae2436 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -157,10 +157,11 @@ public final class PlaybackState implements Parcelable {
/**
* State indicating the class doing playback is currently connecting to a
- * route. Depending on the implementation you may return to the previous
- * state when the connection finishes or enter {@link #STATE_NONE}. If
- * the connection failed {@link #STATE_ERROR} should be used.
- * @hide
+ * new destination. Depending on the implementation you may return to the previous
+ * state when the connection finishes or enter {@link #STATE_NONE}.
+ * If the connection failed {@link #STATE_ERROR} should be used.
+ *
+ * @see #setState
*/
public final static int STATE_CONNECTING = 8;
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
deleted file mode 100644
index 935eb5b..0000000
--- a/media/java/android/media/session/Route.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Represents a destination which an application has connected to and may send
- * media content.
- * <p>
- * This allows a session owner to interact with a route it has been connected
- * to. The MediaRoute must be used to get {@link RouteInterface}
- * instances which can be used to communicate over a specific interface on the
- * route.
- * @hide
- */
-public final class Route {
- private static final String TAG = "Route";
- private final RouteInfo mInfo;
- private final MediaSession mSession;
- private final RouteOptions mOptions;
-
- /**
- * @hide
- */
- public Route(RouteInfo info, RouteOptions options, MediaSession session) {
- if (info == null || options == null) {
- throw new IllegalStateException("Route info was not valid!");
- }
- mInfo = info;
- mOptions = options;
- mSession = session;
- }
-
- /**
- * Get the {@link RouteInfo} for this route.
- *
- * @return The info for this route.
- */
- public RouteInfo getRouteInfo() {
- return mInfo;
- }
-
- /**
- * Get the {@link RouteOptions} that were used to connect this route.
- *
- * @return The options used to connect to this route.
- */
- public RouteOptions getOptions() {
- return mOptions;
- }
-
- /**
- * Gets an interface provided by this route. If the interface is not
- * supported by the route, returns null.
- *
- * @see RouteInterface
- * @param iface The name of the interface to create
- * @return A {@link RouteInterface} or null if the interface is
- * not supported.
- */
- public RouteInterface getInterface(String iface) {
- if (TextUtils.isEmpty(iface)) {
- throw new IllegalArgumentException("iface may not be empty.");
- }
- List<String> ifaces = mOptions.getInterfaceNames();
- if (ifaces != null) {
- for (int i = ifaces.size() - 1; i >= 0; i--) {
- if (iface.equals(ifaces.get(i))) {
- return new RouteInterface(this, iface, mSession);
- }
- }
- }
- Log.e(TAG, "Interface not supported by route");
- return null;
- }
-
- /**
- * @hide
- */
- MediaSession getSession() {
- return mSession;
- }
-}
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
deleted file mode 100644
index 358bc0a..0000000
--- a/media/java/android/media/session/RouteCommand.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents a command that an application may send to a route.
- * <p>
- * Commands are associated with a specific route and interface supported by that
- * route and sent through the session. This class isn't used directly by apps.
- *
- * @hide
- */
-public final class RouteCommand implements Parcelable {
- private final String mRoute;
- private final String mIface;
- private final String mEvent;
- private final Bundle mExtras;
-
- /**
- * @param route The id of the route this event is being sent on
- * @param iface The interface the sender used
- * @param event The event or command
- * @param extras Any extras included with the event
- */
- public RouteCommand(String route, String iface, String event, Bundle extras) {
- mRoute = route;
- mIface = iface;
- mEvent = event;
- mExtras = extras;
- }
-
- private RouteCommand(Parcel in) {
- mRoute = in.readString();
- mIface = in.readString();
- mEvent = in.readString();
- mExtras = in.readBundle();
- }
-
- /**
- * Get the id for the route this event was sent on.
- *
- * @return The route id this event is using
- */
- public String getRouteInfo() {
- return mRoute;
- }
-
- /**
- * Get the interface this event was sent from
- *
- * @return The interface for this event
- */
- public String getIface() {
- return mIface;
- }
-
- /**
- * Get the action/name of the event.
- *
- * @return The name of event/command.
- */
- public String getEvent() {
- return mEvent;
- }
-
- /**
- * Get any extras included with the event.
- *
- * @return The bundle included with the event or null
- */
- public Bundle getExtras() {
- return mExtras;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mRoute);
- dest.writeString(mIface);
- dest.writeString(mEvent);
- dest.writeBundle(mExtras);
- }
-
- public static final Parcelable.Creator<RouteCommand> CREATOR
- = new Parcelable.Creator<RouteCommand>() {
- @Override
- public RouteCommand createFromParcel(Parcel in) {
- return new RouteCommand(in);
- }
-
- @Override
- public RouteCommand[] newArray(int size) {
- return new RouteCommand[size];
- }
- };
-}
diff --git a/media/java/android/media/session/RouteEvent.java b/media/java/android/media/session/RouteEvent.java
deleted file mode 100644
index 918e410..0000000
--- a/media/java/android/media/session/RouteEvent.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.media.routeprovider.RouteConnection;
-import android.media.routeprovider.RouteProviderService;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents an event that a route provider is sending to a particular
- * {@link RouteConnection}. Events are associated with a specific interface
- * supported by the connection and sent through the {@link RouteProviderService}.
- * This class isn't used directly by apps.
- *
- * @hide
- */
-public class RouteEvent implements Parcelable {
- private final IBinder mConnection;
- private final String mIface;
- private final String mEvent;
- private final Bundle mExtras;
-
- /**
- * @param connection The connection that this event is for
- * @param iface The interface the sender used
- * @param event The event or command
- * @param extras Any extras included with the event
- */
- public RouteEvent(IBinder connection, String iface, String event, Bundle extras) {
- mConnection = connection;
- mIface = iface;
- mEvent = event;
- mExtras = extras;
- }
-
- private RouteEvent(Parcel in) {
- mConnection = in.readStrongBinder();
- mIface = in.readString();
- mEvent = in.readString();
- mExtras = in.readBundle();
- }
-
- /**
- * Get the connection this event was sent on.
- *
- * @return The connection this event is using
- */
- public IBinder getConnection() {
- return mConnection;
- }
-
- /**
- * Get the interface this event was sent from
- *
- * @return The interface for this event
- */
- public String getIface() {
- return mIface;
- }
-
- /**
- * Get the action/name of the event.
- *
- * @return The name of event/command.
- */
- public String getEvent() {
- return mEvent;
- }
-
- /**
- * Get any extras included with the event.
- *
- * @return The bundle included with the event or null
- */
- public Bundle getExtras() {
- return mExtras;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(mConnection);
- dest.writeString(mIface);
- dest.writeString(mEvent);
- dest.writeBundle(mExtras);
- }
-
- public static final Parcelable.Creator<RouteEvent> CREATOR
- = new Parcelable.Creator<RouteEvent>() {
- @Override
- public RouteEvent createFromParcel(Parcel in) {
- return new RouteEvent(in);
- }
-
- @Override
- public RouteEvent[] newArray(int size) {
- return new RouteEvent[size];
- }
- };
-}
diff --git a/media/java/android/media/session/RouteInfo.java b/media/java/android/media/session/RouteInfo.java
deleted file mode 100644
index 02f78f9..0000000
--- a/media/java/android/media/session/RouteInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Information about a route, including its display name, a way to identify it,
- * and the ways it can be connected to.
- * @hide
- */
-public final class RouteInfo implements Parcelable {
- private final String mName;
- private final String mId;
- private final String mProviderId;
- private final List<RouteOptions> mOptions;
-
- private RouteInfo(String id, String name, String providerId,
- List<RouteOptions> connRequests) {
- mId = id;
- mName = name;
- mProviderId = providerId;
- mOptions = connRequests;
- }
-
- private RouteInfo(Parcel in) {
- mId = in.readString();
- mName = in.readString();
- mProviderId = in.readString();
- mOptions = new ArrayList<RouteOptions>();
- in.readTypedList(mOptions, RouteOptions.CREATOR);
- }
-
- /**
- * Get the displayable name of this route.
- *
- * @return A short, user readable name for this route
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Get the unique id for this route.
- *
- * @return A unique route id.
- */
- public String getId() {
- return mId;
- }
-
- /**
- * Get the package name of this route's provider.
- *
- * @return The package name of this route's provider.
- */
- public String getProvider() {
- return mProviderId;
- }
-
- /**
- * Get the set of connections that may be used with this route.
- *
- * @return An array of connection requests that may be used to connect
- */
- public List<RouteOptions> getConnectionMethods() {
- return mOptions;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mId);
- dest.writeString(mName);
- dest.writeString(mProviderId);
- dest.writeTypedList(mOptions);
- }
-
- @Override
- public String toString() {
- StringBuilder bob = new StringBuilder();
- bob.append("RouteInfo: id=").append(mId).append(", name=").append(mName)
- .append(", provider=").append(mProviderId).append(", options={");
- for (int i = 0; i < mOptions.size(); i++) {
- if (i != 0) {
- bob.append(", ");
- }
- bob.append(mOptions.get(i).toString());
- }
- bob.append("}");
- return bob.toString();
- }
-
- 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];
- }
- };
-
- /**
- * Helper for creating MediaRouteInfos. A route must have a name and an id.
- * While options are not strictly required the route cannot be connected to
- * without at least one set of options.
- */
- public static final class Builder {
- private String mName;
- private String mId;
- private String mProviderPackage;
- private ArrayList<RouteOptions> mOptions;
-
- /**
- * Copies an existing route info object. TODO Remove once we have
- * helpers for creating route infos.
- *
- * @param from The existing info to copy.
- */
- public Builder(RouteInfo from) {
- mOptions = new ArrayList<RouteOptions>(from.getConnectionMethods());
- mName = from.mName;
- mId = from.mId;
- mProviderPackage = from.mProviderId;
- }
-
- public Builder() {
- mOptions = new ArrayList<RouteOptions>();
- }
-
- /**
- * Set the user visible name for this route.
- *
- * @param name The name of the route
- * @return The builder for easy chaining.
- */
- public Builder setName(String name) {
- mName = name;
- return this;
- }
-
- /**
- * Set the id of the route. This should be unique to the provider.
- *
- * @param id The unique id of the route.
- * @return The builder for easy chaining.
- */
- public Builder setId(String id) {
- mId = id;
- return this;
- }
-
- /**
- * @hide
- */
- public Builder setProviderId(String packageName) {
- mProviderPackage = packageName;
- return this;
- }
-
- /**
- * Add a set of {@link RouteOptions} to the route. Multiple options
- * may be added to the same route.
- *
- * @param options The options to add to this route.
- * @return The builder for easy chaining.
- */
- public Builder addRouteOptions(RouteOptions options) {
- mOptions.add(options);
- return this;
- }
-
- /**
- * Clear the set of {@link RouteOptions} on the route.
- *
- * @return The builder for easy chaining
- */
- public Builder clearRouteOptions() {
- mOptions.clear();
- return this;
- }
-
- /**
- * Build a new MediaRouteInfo.
- *
- * @return A new MediaRouteInfo with the values that were set.
- */
- public RouteInfo build() {
- if (TextUtils.isEmpty(mName)) {
- throw new IllegalArgumentException("Must set a name before building");
- }
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("Must set an id before building");
- }
- return new RouteInfo(mId, mName, mProviderPackage, mOptions);
- }
-
- /**
- * Get the current number of options that have been added to this
- * builder.
- *
- * @return The number of options that have been added.
- */
- public int getOptionsSize() {
- return mOptions.size();
- }
- }
-}
diff --git a/media/java/android/media/session/RouteInterface.java b/media/java/android/media/session/RouteInterface.java
deleted file mode 100644
index 8de4d89..0000000
--- a/media/java/android/media/session/RouteInterface.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * A route can support multiple interfaces for a {@link MediaSession} to
- * interact with. To use a specific interface with a route a
- * MediaSessionRouteInterface needs to be retrieved from the route. An
- * implementation of the specific interface, like
- * {@link RoutePlaybackControls}, should be used to simplify communication
- * and reduce errors on that interface.
- *
- * @see RoutePlaybackControls for an example
- * @hide
- */
-public final class RouteInterface {
- private static final String TAG = "RouteInterface";
-
- /**
- * Error indicating the route is currently not connected.
- */
- public static final int RESULT_NOT_CONNECTED = -5;
- /**
- * Error indicating the session is no longer using the route this command
- * was sent to.
- */
- public static final int RESULT_ROUTE_IS_STALE = -4;
- /**
- * Error indicating that the interface does not support the command.
- */
- public static final int RESULT_COMMAND_NOT_SUPPORTED = -3;
- /**
- * Error indicating that the route does not support the interface.
- */
- public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2;
- /**
- * Generic error. Extra information about the error may be included in the
- * result bundle.
- */
- public static final int RESULT_ERROR = -1;
- /**
- * The command was successful. Extra information may be included in the
- * result bundle.
- */
- public static final int RESULT_SUCCESS = 1;
-
- private final Route mRoute;
- private final String mIface;
- private final MediaSession mSession;
-
- private final Object mLock = new Object();
- private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
-
- /**
- * @hide
- */
- RouteInterface(Route route, String iface, MediaSession session) {
- mRoute = route;
- mIface = iface;
- mSession = session;
- mSession.addInterfaceListener(iface, mEventListener);
- }
-
- /**
- * Send a command using this interface.
- *
- * @param command The command to send.
- * @param extras Any extras to include with the command.
- * @param cb The callback to receive the result on.
- * @return true if the command was sent, false otherwise.
- */
- public boolean sendCommand(String command, Bundle extras, ResultReceiver cb) {
- RouteCommand cmd = new RouteCommand(mRoute.getRouteInfo().getId(), mIface,
- command, extras);
- return mSession.sendRouteCommand(cmd, cb);
- }
-
- /**
- * Add a listener to this interface. Events will be sent on the caller's
- * thread.
- *
- * @param listener The listener to receive events on.
- */
- public void addListener(EventListener listener) {
- addListener(listener, null);
- }
-
- /**
- * Add a listener for this interface. If a handler is specified events will
- * be performed on the handler's thread, otherwise the caller's thread will
- * be used.
- *
- * @param listener The listener to receive events on
- * @param handler The handler whose thread to post calls on
- */
- public void addListener(EventListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- if (handler == null) {
- handler = new Handler();
- }
- synchronized (mLock) {
- if (findIndexOfListenerLocked(listener) != -1) {
- Log.d(TAG, "Listener is already added, ignoring");
- return;
- }
- mListeners.add(new EventHandler(handler.getLooper(), listener));
- }
- }
-
- /**
- * Remove a listener from this interface.
- *
- * @param listener The listener to stop receiving events on.
- */
- public void removeListener(EventListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- synchronized (mLock) {
- int index = findIndexOfListenerLocked(listener);
- if (index != -1) {
- mListeners.remove(index);
- }
- }
- }
-
- private int findIndexOfListenerLocked(EventListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- EventHandler handler = mListeners.get(i);
- if (listener == handler.mListener) {
- return i;
- }
- }
- return -1;
- }
-
- private EventListener mEventListener = new EventListener() {
- @Override
- public void onEvent(String event, Bundle args) {
- synchronized (mLock) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).postEvent(event, args);
- }
- }
- }
-
- };
-
- /**
- * An EventListener can be registered by an app with TODO to handle events
- * sent by the session on a specific interface.
- */
- public static abstract class EventListener {
- /**
- * This is called when an event is received from the interface. Events
- * are sent by the session owner and will be delivered to all
- * controllers that are listening to the interface.
- *
- * @param event The event that occurred.
- * @param args Any extras that were included with the event. May be
- * null.
- */
- public abstract void onEvent(String event, Bundle args);
- }
-
- private static final class EventHandler extends Handler {
-
- private final EventListener mListener;
-
- public EventHandler(Looper looper, EventListener cb) {
- super(looper, null, true);
- mListener = cb;
- }
-
- @Override
- public void handleMessage(Message msg) {
- mListener.onEvent((String) msg.obj, msg.getData());
- }
-
- public void postEvent(String event, Bundle args) {
- Message msg = obtainMessage(0, event);
- msg.setData(args);
- msg.sendToTarget();
- }
- }
-}
diff --git a/media/java/android/media/session/RouteOptions.aidl b/media/java/android/media/session/RouteOptions.aidl
deleted file mode 100644
index feaf517..0000000
--- a/media/java/android/media/session/RouteOptions.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2014, 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.session;
-
-parcelable RouteOptions;
diff --git a/media/java/android/media/session/RouteOptions.java b/media/java/android/media/session/RouteOptions.java
deleted file mode 100644
index b4fb341..0000000
--- a/media/java/android/media/session/RouteOptions.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Specifies options that an application might use when connecting to a route.
- * This includes things like interfaces, connection parameters, and required
- * features.
- * <p>
- * An application may create several different route options that describe
- * alternative sets of capabilities that it can use and choose the most
- * appropriate route options when it is ready to connect to the route. Each
- * route options instance must specify a complete set of capabilities to request
- * when the connection is established.
- * @hide
- */
-public final class RouteOptions implements Parcelable {
- private static final String TAG = "RouteOptions";
-
- private final ArrayList<String> mIfaces;
- private final Bundle mConnectionParams;
-
- private RouteOptions(List<String> ifaces, Bundle params) {
- mIfaces = new ArrayList<String>(ifaces);
- mConnectionParams = params;
- }
-
- private RouteOptions(Parcel in) {
- mIfaces = new ArrayList<String>();
- in.readStringList(mIfaces);
- mConnectionParams = in.readBundle();
- }
-
- /**
- * Get the interfaces this connection wants to use.
- *
- * @return The interfaces for this connection
- */
- public List<String> getInterfaceNames() {
- return mIfaces;
- }
-
- /**
- * Get the parameters that will be used for connecting.
- *
- * @return The set of connection parameters this connections uses
- */
- public Bundle getConnectionParams() {
- return mConnectionParams;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStringList(mIfaces);
- dest.writeBundle(mConnectionParams);
- }
-
- @Override
- public String toString() {
- StringBuilder bob = new StringBuilder();
- bob.append("Options: interfaces={");
- for (int i = 0; i < mIfaces.size(); i++) {
- if (i != 0) {
- bob.append(", ");
- }
- bob.append(mIfaces.get(i));
- }
- bob.append("}");
- bob.append(", parameters=");
- bob.append(mConnectionParams == null ? "null" : mConnectionParams.toString());
- return bob.toString();
- }
-
- public static final Parcelable.Creator<RouteOptions> CREATOR
- = new Parcelable.Creator<RouteOptions>() {
- @Override
- public RouteOptions createFromParcel(Parcel in) {
- return new RouteOptions(in);
- }
-
- @Override
- public RouteOptions[] newArray(int size) {
- return new RouteOptions[size];
- }
- };
-
- /**
- * Builder for creating {@link RouteOptions}.
- */
- public final static class Builder {
- private ArrayList<String> mIfaces = new ArrayList<String>();
- private Bundle mConnectionParams;
-
- public Builder() {
- }
-
- /**
- * Add a required interface to the options.
- *
- * @param interfaceName The name of the interface to add.
- * @return The builder to allow chaining commands.
- */
- public Builder addInterface(String interfaceName) {
- if (TextUtils.isEmpty(interfaceName)) {
- throw new IllegalArgumentException("interfaceName cannot be empty");
- }
- if (!mIfaces.contains(interfaceName)) {
- mIfaces.add(interfaceName);
- } else {
- Log.w(TAG, "Attempted to add interface that is already added");
- }
- return this;
- }
-
- /**
- * Set the connection parameters to use with the options. TODO replace
- * with more specific calls once we decide on the standard way to
- * express parameters.
- *
- * @param parameters The parameters to use.
- * @return The builder to allow chaining commands.
- */
- public Builder setParameters(Bundle parameters) {
- mConnectionParams = parameters;
- return this;
- }
-
- /**
- * Generate a set of options.
- *
- * @return The options with the specified components.
- */
- public RouteOptions build() {
- return new RouteOptions(mIfaces, mConnectionParams);
- }
- }
-}
diff --git a/media/java/android/media/session/RoutePlaybackControls.java b/media/java/android/media/session/RoutePlaybackControls.java
deleted file mode 100644
index 8211983..0000000
--- a/media/java/android/media/session/RoutePlaybackControls.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2014 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.session;
-
-import android.media.MediaMetadata;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-
-/**
- * A standard media control interface for Routes that support queueing and
- * transport controls. Routes may support multiple interfaces for MediaSessions
- * to interact with.
- * @hide
- */
-public final class RoutePlaybackControls {
- private static final String TAG = "RoutePlaybackControls";
- public static final String NAME = "android.media.session.RoutePlaybackControls";
-
- /** @hide */
- public static final String KEY_VALUE1 = "value1";
-
- /** @hide */
- public static final String CMD_FAST_FORWARD = "fastForward";
- /** @hide */
- public static final String CMD_GET_CURRENT_POSITION = "getCurrentPosition";
- /** @hide */
- public static final String CMD_GET_CAPABILITIES = "getCapabilities";
- /** @hide */
- public static final String CMD_PLAY_NOW = "playNow";
- /** @hide */
- public static final String CMD_RESUME = "resume";
- /** @hide */
- public static final String CMD_PAUSE = "pause";
-
- /** @hide */
- public static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
- /** @hide */
- public static final String EVENT_METADATA_CHANGE = "metadataChange";
-
- private final RouteInterface mIface;
-
- private RoutePlaybackControls(RouteInterface iface) {
- mIface = iface;
- }
-
- /**
- * Get a new MediaRoutePlaybackControls instance for sending commands using
- * this interface. If the provided route doesn't support this interface null
- * will be returned.
- *
- * @param route The route to send commands to.
- * @return A MediaRoutePlaybackControls instance or null if not supported.
- */
- public static RoutePlaybackControls from(Route route) {
- RouteInterface iface = route.getInterface(NAME);
- if (iface != null) {
- return new RoutePlaybackControls(iface);
- }
- return null;
- }
-
- /**
- * Send a resume command to the route.
- */
- public void resume() {
- mIface.sendCommand(CMD_RESUME, null, null);
- }
-
- /**
- * Send a pause command to the route.
- */
- public void pause() {
- mIface.sendCommand(CMD_PAUSE, null, null);
- }
-
- /**
- * Send a fast forward command.
- */
- public void fastForward() {
- Bundle b = new Bundle();
- mIface.sendCommand(CMD_FAST_FORWARD, b, null);
- }
-
- /**
- * Retrieves the current playback position.
- *
- * @param cb The callback to receive the result on.
- */
- public void getCurrentPosition(ResultReceiver cb) {
- mIface.sendCommand(CMD_GET_CURRENT_POSITION, null, cb);
- }
-
- public void getCapabilities(ResultReceiver cb) {
- mIface.sendCommand(CMD_GET_CAPABILITIES, null, cb);
- }
-
- public void addListener(Listener listener) {
- mIface.addListener(listener);
- }
-
- public void addListener(Listener listener, Handler handler) {
- mIface.addListener(listener, handler);
- }
-
- public void removeListener(Listener listener) {
- mIface.removeListener(listener);
- }
-
- public void playNow(String content) {
- Bundle bundle = new Bundle();
- bundle.putString(KEY_VALUE1, content);
- mIface.sendCommand(CMD_PLAY_NOW, bundle, null);
- }
-
- /**
- * Register this event listener using {@link #addListener} to receive
- * RoutePlaybackControl events from a session.
- */
- public static abstract class Listener extends RouteInterface.EventListener {
- @Override
- public final void onEvent(String event, Bundle args) {
- if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
- onPlaybackStateChange(args.getInt(KEY_VALUE1, 0));
- } else if (EVENT_METADATA_CHANGE.equals(event)) {
- onMetadataUpdate((MediaMetadata) args.getParcelable(KEY_VALUE1));
- }
- }
-
- /**
- * Override to handle updates to the playback state. Valid values are in
- * {@link TransportPerformer}. TODO put playstate values somewhere more
- * generic.
- *
- * @param state
- */
- public void onPlaybackStateChange(int state) {
- }
-
- /**
- * Override to handle metadata changes for this session's media. The
- * default supported fields are those in {@link MediaMetadata}.
- *
- * @param metadata
- */
- public void onMetadataUpdate(MediaMetadata metadata) {
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
deleted file mode 100644
index b31153b..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2014 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.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.routeprovider.IRouteConnection;
-import android.media.routeprovider.IRouteProvider;
-import android.media.routeprovider.IRouteProviderCallback;
-import android.media.routeprovider.RouteProviderService;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteEvent;
-import android.media.session.RouteInfo;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Slog;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * System representation and interface to a MediaRouteProvider. This class is
- * not thread safe so all calls should be made on the main thread.
- */
-public class MediaRouteProviderProxy {
- private static final String TAG = "MRPProxy";
- private static final boolean DEBUG = true;
-
- private static final int MAX_RETRIES = 3;
-
- private final Object mLock = new Object();
- private final Context mContext;
- private final String mId;
- private final ComponentName mComponentName;
- private final int mUserId;
- // Interfaces declared in the manifest
- private final ArrayList<String> mInterfaces = new ArrayList<String>();
- private final ArrayList<RouteConnectionRecord> mConnections
- = new ArrayList<RouteConnectionRecord>();
- // The sessions that have a route from this provider selected
- private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
- private final Handler mHandler = new Handler();
-
- private Intent mBindIntent;
- private IRouteProvider mBinder;
- private boolean mRunning;
- private boolean mPaused;
- private boolean mInterested;
- private boolean mBound;
- private int mRetryCount;
-
- private RoutesListener mRouteListener;
-
- public MediaRouteProviderProxy(Context context, String id, ComponentName component, int uid,
- ArrayList<String> interfaces) {
- mContext = context;
- mId = id;
- mComponentName = component;
- mUserId = uid;
- if (interfaces != null) {
- mInterfaces.addAll(interfaces);
- }
- mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
- mBindIntent.setComponent(mComponentName);
- }
-
- public void destroy() {
- stop();
- mSessions.clear();
- updateBinding();
- }
-
- /**
- * Send any cleanup messages and unbind from the media route provider
- */
- public void stop() {
- if (mRunning) {
- mRunning = false;
- mRetryCount = 0;
- updateBinding();
- }
- }
-
- /**
- * Bind to the media route provider and perform any setup needed
- */
- public void start() {
- if (!mRunning) {
- mRunning = true;
- updateBinding();
- }
- }
-
- /**
- * Set whether or not this provider is currently interesting to the system.
- * In the future this may take a list of interfaces instead.
- *
- * @param interested True if we want to connect to this provider
- */
- public void setInterested(boolean interested) {
- mInterested = interested;
- updateBinding();
- }
-
- /**
- * Set a listener to get route updates on.
- *
- * @param listener The listener to receive updates on.
- */
- public void setRoutesListener(RoutesListener listener) {
- mRouteListener = listener;
- }
-
- /**
- * Send a request to the Provider to get all the routes that the session can
- * use.
- *
- * @param record The session to get routes for.
- * @param requestId An id to identify this request.
- */
- public void getRoutes(MediaSessionRecord record, final int requestId) {
- // TODO change routes to have a system global id and maintain a mapping
- // to the original route
- if (mBinder == null) {
- Log.wtf(TAG, "Attempted to call getRoutes without a binder connection");
- return;
- }
- List<RouteRequest> requests = record.getRouteRequests();
- final String sessionId = record.getSessionInfo().getId();
- try {
- mBinder.getAvailableRoutes(requests, new ResultReceiver(mHandler) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != RouteProviderService.RESULT_SUCCESS) {
- // ignore failures, just means no routes were generated
- return;
- }
- ArrayList<RouteInfo> routes
- = resultData.getParcelableArrayList(RouteProviderService.KEY_ROUTES);
- ArrayList<RouteInfo> sysRoutes = new ArrayList<RouteInfo>();
- for (int i = 0; i < routes.size(); i++) {
- RouteInfo route = routes.get(i);
- RouteInfo.Builder bob = new RouteInfo.Builder(route);
- bob.setProviderId(mId);
- sysRoutes.add(bob.build());
- }
- if (mRouteListener != null) {
- mRouteListener.onRoutesUpdated(sessionId, sysRoutes, requestId);
- }
- }
- });
- } catch (RemoteException e) {
- Log.d(TAG, "Error in getRoutes", e);
- }
- }
-
- /**
- * Try connecting again if we've been disconnected.
- */
- public void rebindIfDisconnected() {
- if (mBinder == null && shouldBind()) {
- unbind();
- bind();
- }
- }
-
- /**
- * Send a request to connect to a route.
- *
- * @param session The session that is trying to connect.
- * @param route The route it is connecting to.
- * @param request The request with the connection parameters.
- * @return true if the request was sent, false otherwise.
- */
- public boolean connectToRoute(MediaSessionRecord session, final RouteInfo route,
- final RouteRequest request) {
- final String sessionId = session.getSessionInfo().getId();
- try {
- mBinder.connect(route, request, new ResultReceiver(mHandler) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != RouteProviderService.RESULT_SUCCESS) {
- // TODO handle connection failure
- return;
- }
- IBinder binder = resultData.getBinder(RouteProviderService.KEY_CONNECTION);
- IRouteConnection connection = null;
- if (binder != null) {
- connection = IRouteConnection.Stub.asInterface(binder);
- }
-
- if (connection != null) {
- RouteConnectionRecord record = new RouteConnectionRecord(
- connection, mComponentName.getPackageName(), mUserId);
- mConnections.add(record);
- if (mRouteListener != null) {
- mRouteListener.onRouteConnected(sessionId, route, request, record);
- }
- }
- }
- });
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting to route.", e);
- return false;
- }
- return true;
- }
-
- /**
- * Check if this is the provider you're looking for.
- */
- public boolean hasComponentName(String packageName, String className) {
- return mComponentName.getPackageName().equals(packageName)
- && mComponentName.getClassName().equals(className);
- }
-
- /**
- * Get the unique id for this provider.
- *
- * @return The provider's id.
- */
- public String getId() {
- return mId;
- }
-
- public void addSession(MediaSessionRecord session) {
- mSessions.add(session);
- }
-
- public void removeSession(MediaSessionRecord session) {
- mSessions.remove(session);
- updateBinding();
- }
-
- public int getSessionCount() {
- return mSessions.size();
- }
-
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + mId + " " + this);
- String indent = prefix + " ";
-
- pw.println(indent + "component=" + mComponentName.toString());
- pw.println(indent + "user id=" + mUserId);
- pw.println(indent + "interfaces=" + mInterfaces.toString());
- pw.println(indent + "connections=" + mConnections.toString());
- pw.println(indent + "running=" + mRunning);
- pw.println(indent + "interested=" + mInterested);
- pw.println(indent + "bound=" + mBound);
- }
-
- private void updateBinding() {
- if (shouldBind()) {
- bind();
- } else {
- unbind();
- }
- }
-
- // We want to bind as long as we're interested in this provider or there are
- // sessions connected to it.
- private boolean shouldBind() {
- return (mRunning && mInterested) || (!mSessions.isEmpty());
- }
-
- private void bind() {
- if (!mBound) {
- if (DEBUG) {
- Slog.d(TAG, this + ": Binding");
- }
-
- try {
- mBound = mContext.bindServiceAsUser(mBindIntent, mServiceConn,
- 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;
- mContext.unbindService(mServiceConn);
- }
- }
-
- private RouteConnectionRecord getConnectionLocked(IBinder binder) {
- for (int i = mConnections.size() - 1; i >= 0; i--) {
- RouteConnectionRecord record = mConnections.get(i);
- if (record.isConnection(binder)) {
- return record;
- }
- }
- return null;
- }
-
- private ServiceConnection mServiceConn = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mBinder = IRouteProvider.Stub.asInterface(service);
- if (DEBUG) {
- Slog.d(TAG, "Connected to route provider");
- }
- try {
- mBinder.registerCallback(mCbStub);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error registering callback on route provider. Retry count: "
- + mRetryCount, e);
- if (mRetryCount < MAX_RETRIES) {
- mRetryCount++;
- rebindIfDisconnected();
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mBinder = null;
- if (DEBUG) {
- Slog.d(TAG, "Disconnected from route provider");
- }
- }
-
- };
-
- private IRouteProviderCallback.Stub mCbStub = new IRouteProviderCallback.Stub() {
- @Override
- public void onConnectionStateChanged(IRouteConnection connection, int state)
- throws RemoteException {
- // TODO
- }
-
- @Override
- public void onRouteEvent(RouteEvent event) throws RemoteException {
- synchronized (mLock) {
- RouteConnectionRecord record = getConnectionLocked(event.getConnection());
- Log.d(TAG, "Received route event for record " + record);
- if (record != null) {
- record.sendEvent(event);
- }
- }
- }
-
- @Override
- public void onConnectionTerminated(IRouteConnection connection) throws RemoteException {
- synchronized (mLock) {
- RouteConnectionRecord record = getConnectionLocked(connection.asBinder());
- if (record != null) {
- record.disconnect();
- mConnections.remove(record);
- }
- }
- }
-
- @Override
- public void onRoutesChanged() throws RemoteException {
- // TODO
- }
- };
-
- /**
- * Listener for receiving responses to route requests on the provider.
- */
- public interface RoutesListener {
- /**
- * Called when routes have been returned from a request to getRoutes.
- *
- * @param record The session that the routes were requested for.
- * @param routes The matching routes returned by the provider.
- * @param reqId The request id this is responding to.
- */
- public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
- int reqId);
-
- /**
- * Called when a route has successfully connected.
- *
- * @param session The session that was connected.
- * @param route The route it connected to.
- * @param options The options that were used for the connection.
- * @param connection The connection instance that was created.
- */
- public void onRouteConnected(String sessionId, RouteInfo route,
- RouteRequest options, RouteConnectionRecord connection);
- }
-}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
deleted file mode 100644
index 734eab9..0000000
--- a/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2014 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.Manifest;
-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.routeprovider.RouteProviderService;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.UUID;
-
-/**
- * Watches for media route provider services to be installed. Adds a provider to
- * the media session service for each registered service. For now just run all
- * providers. In the future define a policy for when to run providers.
- */
-public class MediaRouteProviderWatcher {
- private static final String TAG = "MRPWatcher";
- private static final boolean DEBUG = true; // 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<MediaRouteProviderProxy> mProviders =
- new ArrayList<MediaRouteProviderProxy>();
- private boolean mRunning;
-
- public MediaRouteProviderWatcher(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 + " 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);
- }
- }
-
- // Stop discovering providers and routes. Providers that still have an
- // active session connected to them will not unbind.
- public void stop() {
- if (mRunning) {
- mRunning = false;
-
- mContext.unregisterReceiver(mScanPackagesReceiver);
- mHandler.removeCallbacks(mScanPackagesRunnable);
-
- // Stop all inactive providers.
- for (int i = mProviders.size() - 1; i >= 0; i--) {
- mProviders.get(i).stop();
- }
- }
- }
-
- // Clean up the providers forcibly unbinding if necessary
- public void destroy() {
- for (int i = mProviders.size() - 1; i >= 0; i--) {
- mProviders.get(i).destroy();
- mProviders.remove(i);
- }
- }
-
- public ArrayList<MediaRouteProviderProxy> getProviders() {
- return mProviders;
- }
-
- public MediaRouteProviderProxy getProvider(String id) {
- int providerIndex = findProvider(id);
- if (providerIndex != -1) {
- return mProviders.get(providerIndex);
- }
- return null;
- }
-
- 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(RouteProviderService.SERVICE_INTERFACE);
- for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
- intent, 0, mUserId)) {
- ServiceInfo serviceInfo = resolveInfo.serviceInfo;
- if (DEBUG) {
- Slog.d(TAG, "Checking service " + (serviceInfo == null ? null : serviceInfo.name));
- }
- if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
- int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
- if (sourceIndex < 0) {
- // TODO get declared interfaces from manifest
- if (DEBUG) {
- Slog.d(TAG, "Creating new provider proxy for service");
- }
- MediaRouteProviderProxy provider =
- new MediaRouteProviderProxy(mContext, UUID.randomUUID().toString(),
- new ComponentName(serviceInfo.packageName, serviceInfo.name),
- mUserId, null);
- provider.start();
- mProviders.add(targetIndex++, provider);
- mCallback.addProvider(provider);
- } else if (sourceIndex >= targetIndex) {
- MediaRouteProviderProxy 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--) {
- MediaRouteProviderProxy provider = mProviders.get(i);
- mCallback.removeProvider(provider);
- mProviders.remove(provider);
- provider.stop();
- }
- }
- }
-
- private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
- if (serviceInfo.permission == null || !serviceInfo.permission.equals(
- Manifest.permission.BIND_ROUTE_PROVIDER)) {
- // If the service does not require this permission then any app
- // could potentially bind to it and mess with their routes. So we
- // only want to trust providers that require the
- // correct permissions.
- Slog.w(TAG, "Ignoring route provider service because it did not "
- + "require the BIND_ROUTE_PROVIDER permission in its manifest: "
- + serviceInfo.packageName + "/" + serviceInfo.name);
- return false;
- }
- // Looks good.
- return true;
- }
-
- private int findProvider(String id) {
- int count = mProviders.size();
- for (int i = 0; i < count; i++) {
- MediaRouteProviderProxy provider = mProviders.get(i);
- if (TextUtils.equals(id, provider.getId())) {
- return i;
- }
- }
- return -1;
- }
-
- private int findProvider(String packageName, String className) {
- int count = mProviders.size();
- for (int i = 0; i < count; i++) {
- MediaRouteProviderProxy 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(MediaRouteProviderProxy provider);
-
- void removeProvider(MediaRouteProviderProxy provider);
- }
-}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0fbcd7e..341c7a9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -19,19 +19,16 @@ package com.android.server.media;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.media.routeprovider.RouteRequest;
+import android.media.routing.IMediaRouter;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
import android.media.session.ISessionController;
import android.media.session.ISessionControllerCallback;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.MediaController;
-import android.media.session.RouteCommand;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RouteEvent;
import android.media.session.MediaSession;
import android.media.session.MediaSessionInfo;
-import android.media.session.RouteInterface;
import android.media.session.PlaybackState;
import android.media.session.ParcelableVolumeInfo;
import android.media.AudioManager;
@@ -94,14 +91,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private final Object mLock = new Object();
private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
new ArrayList<ISessionControllerCallback>();
- private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
- private RouteInfo mRoute;
- private RouteOptions mRequest;
- private RouteConnectionRecord mConnection;
- // TODO define a RouteState class with relevant info
- private int mRouteState;
private long mFlags;
+ private IMediaRouter mMediaRouter;
private ComponentName mMediaButtonReceiver;
// TransportPerformer fields
@@ -160,24 +152,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
/**
- * Get the set of route requests this session is interested in.
- *
- * @return The list of RouteRequests
- */
- public List<RouteRequest> getRouteRequests() {
- return mRequests;
- }
-
- /**
- * Get the route this session is currently on.
- *
- * @return The route the session is on.
- */
- public RouteInfo getRoute() {
- return mRoute;
- }
-
- /**
* Get the info for this session.
*
* @return Info that identifies this session.
@@ -229,41 +203,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
/**
- * Set the selected route. This does not connect to the route, just notifies
- * the app that a new route has been selected.
- *
- * @param route The route that was selected.
- */
- public void selectRoute(RouteInfo route) {
- synchronized (mLock) {
- if (route != mRoute) {
- disconnect(MediaSession.DISCONNECT_REASON_ROUTE_CHANGED);
- }
- mRoute = route;
- }
- mSessionCb.sendRouteChange(route);
- }
-
- /**
- * Update the state of the route this session is using and notify the
- * session.
- *
- * @param state The new state of the route.
- */
- public void setRouteState(int state) {
- mSessionCb.sendRouteStateChange(state);
- }
-
- /**
- * Send an event to this session from the route it is using.
- *
- * @param event The event to send.
- */
- public void sendRouteEvent(RouteEvent event) {
- mSessionCb.sendRouteEvent(event);
- }
-
- /**
* Send a volume adjustment to the session owner.
*
* @param delta The amount to adjust the volume by.
@@ -338,40 +277,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
/**
- * Set the connection to use for the selected route and notify the app it is
- * now connected.
- *
- * @param route The route the connection is to.
- * @param request The request that was used to connect.
- * @param connection The connection to the route.
- * @return True if this connection is still valid, false if it is stale.
- */
- public boolean setRouteConnected(RouteInfo route, RouteOptions request,
- RouteConnectionRecord connection) {
- synchronized (mLock) {
- if (mDestroyed) {
- Log.i(TAG, "setRouteConnected: session has been destroyed");
- connection.disconnect();
- return false;
- }
- if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
- Log.w(TAG, "setRouteConnected: connected route is stale");
- connection.disconnect();
- return false;
- }
- if (request != mRequest) {
- Log.w(TAG, "setRouteConnected: connection request is stale");
- connection.disconnect();
- return false;
- }
- mConnection = connection;
- mConnection.setListener(mConnectionListener);
- mSessionCb.sendRouteConnected();
- }
- return true;
- }
-
- /**
* Check if this session has been set to active by the app.
*
* @return True if the session is active, false otherwise.
@@ -460,30 +365,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
return mOptimisticVolume;
}
- /**
- * @return True if this session is currently connected to a route.
- */
- public boolean isConnected() {
- return mConnection != null;
- }
-
- public void disconnect(int reason) {
- synchronized (mLock) {
- if (!mDestroyed) {
- disconnectLocked(reason);
- }
- }
- }
-
- private void disconnectLocked(int reason) {
- if (mConnection != null) {
- mConnection.setListener(null);
- mConnection.disconnect();
- mConnection = null;
- pushDisconnected(reason);
- }
- }
-
public boolean isTransportControlEnabled() {
return hasFlag(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
}
@@ -502,11 +383,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
if (mDestroyed) {
return;
}
- if (isConnected()) {
- disconnectLocked(MediaSession.DISCONNECT_REASON_SESSION_DESTROYED);
- }
- mRoute = null;
- mRequest = null;
mDestroyed = true;
}
}
@@ -532,15 +408,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
pw.println(indent + "controllers: " + mControllerCallbacks.size());
pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString()));
pw.println(indent + "metadata:" + getShortMetadataString());
- pw.println(indent + "route requests {");
- int size = mRequests.size();
- for (int i = 0; i < size; i++) {
- pw.println(indent + " " + mRequests.get(i).toString());
- }
- pw.println(indent + "}");
- pw.println(indent + "route=" + (mRoute == null ? null : mRoute.toString()));
- pw.println(indent + "connection=" + (mConnection == null ? null : mConnection.toString()));
- pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString()));
}
private String getShortMetadataString() {
@@ -550,12 +417,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
return "size=" + fields + ", title=" + title;
}
- private void pushDisconnected(int reason) {
- synchronized (mLock) {
- mSessionCb.sendRouteDisconnected(reason);
- }
- }
-
private void pushPlaybackStateUpdate() {
synchronized (mLock) {
if (mDestroyed) {
@@ -613,25 +474,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
- private void pushRouteUpdate() {
- synchronized (mLock) {
- if (mDestroyed) {
- return;
- }
- for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
- ISessionControllerCallback cb = mControllerCallbacks.get(i);
- try {
- cb.onRouteChanged(mRoute);
- } catch (DeadObjectException e) {
- Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
- mControllerCallbacks.remove(i);
- } catch (RemoteException e) {
- Log.w(TAG, "unexpected exception in pushRouteUpdate.", e);
- }
- }
- }
- }
-
private void pushEvent(String event, Bundle data) {
synchronized (mLock) {
if (mDestroyed) {
@@ -651,25 +493,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
- private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
- synchronized (mLock) {
- if (mDestroyed) {
- return;
- }
- if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
- if (cb != null) {
- cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
- return;
- }
- }
- if (mConnection != null) {
- mConnection.sendCommand(command, cb);
- } else if (cb != null) {
- cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
- }
- }
- }
-
private PlaybackState getStateWithUpdatedPosition() {
PlaybackState state = mPlaybackState;
long duration = -1;
@@ -708,21 +531,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
return -1;
}
- private final RouteConnectionRecord.Listener mConnectionListener
- = new RouteConnectionRecord.Listener() {
- @Override
- public void onEvent(RouteEvent event) {
- RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
- event.getEvent(), event.getExtras());
- mSessionCb.sendRouteEvent(eventForSession);
- }
-
- @Override
- public void disconnect() {
- MediaSessionRecord.this.disconnect(MediaSession.DISCONNECT_REASON_PROVIDER_DISCONNECTED);
- }
- };
-
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@Override
public void run() {
@@ -769,6 +577,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
+ public void setMediaRouter(IMediaRouter router) {
+ mMediaRouter = router;
+ mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+ }
+
+ @Override
public void setMediaButtonReceiver(ComponentName mbr) {
mMediaButtonReceiver = mbr;
}
@@ -797,46 +611,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
- mHandler.post(MessageHandler.MSG_SEND_COMMAND,
- new Pair<RouteCommand, ResultReceiver>(command, cb));
- }
-
- @Override
- public boolean setRoute(RouteInfo route) throws RemoteException {
- // TODO decide if allowed to set route and if the route exists
- return false;
- }
-
- @Override
- public void connectToRoute(RouteInfo route, RouteOptions request)
- throws RemoteException {
- if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
- throw new RemoteException("RouteInfo does not match current route");
- }
- mService.connectToRoute(MediaSessionRecord.this, route, request);
- mRequest = request;
- }
-
- @Override
- public void disconnectFromRoute(RouteInfo route) {
- if (route != null && mRoute != null
- && TextUtils.equals(route.getId(), mRoute.getId())) {
- disconnect(MediaSession.DISCONNECT_REASON_SESSION_DISCONNECTED);
- }
- }
-
- @Override
- public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
- mRequests.clear();
- for (int i = options.size() - 1; i >= 0; i--) {
- RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
- false);
- mRequests.add(request);
- }
- }
-
- @Override
public void setCurrentVolume(int volume) {
mCurrentVolume = volume;
mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
@@ -903,46 +677,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
- public void sendRouteChange(RouteInfo route) {
- try {
- mCb.onRequestRouteChange(route);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in sendRouteChange.", e);
- }
- }
-
- public void sendRouteStateChange(int state) {
- try {
- mCb.onRouteStateChange(state);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
- }
- }
-
- public void sendRouteEvent(RouteEvent event) {
- try {
- mCb.onRouteEvent(event);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
- }
- }
-
- public void sendRouteConnected() {
- try {
- mCb.onRouteConnected(mRoute, mRequest);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
- }
- }
-
- public void sendRouteDisconnected(int reason) {
- try {
- mCb.onRouteDisconnected(mRoute, reason);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote failure in sendRouteDisconnected");
- }
- }
-
public void play() {
try {
mCb.onPlay();
@@ -1187,20 +921,19 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public void showRoutePicker() {
- mService.showRoutePickerForSession(MediaSessionRecord.this);
+ public IMediaRouterDelegate createMediaRouterDelegate(
+ IMediaRouterStateCallback callback) {
+ // todo
+ return null;
}
}
private class MessageHandler extends Handler {
private static final int MSG_UPDATE_METADATA = 1;
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
- private static final int MSG_UPDATE_ROUTE = 3;
- private static final int MSG_SEND_EVENT = 4;
- private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
- private static final int MSG_SEND_COMMAND = 6;
- private static final int MSG_UPDATE_SESSION_STATE = 7;
- private static final int MSG_UPDATE_VOLUME = 8;
+ private static final int MSG_SEND_EVENT = 3;
+ private static final int MSG_UPDATE_SESSION_STATE = 4;
+ private static final int MSG_UPDATE_VOLUME = 5;
public MessageHandler(Looper looper) {
super(looper);
@@ -1214,17 +947,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
case MSG_UPDATE_PLAYBACK_STATE:
pushPlaybackStateUpdate();
break;
- case MSG_UPDATE_ROUTE:
- pushRouteUpdate();
- break;
case MSG_SEND_EVENT:
pushEvent((String) msg.obj, msg.getData());
break;
- case MSG_SEND_COMMAND:
- Pair<RouteCommand, ResultReceiver> cmd =
- (Pair<RouteCommand, ResultReceiver>) msg.obj;
- pushRouteCommand(cmd.first, cmd.second);
- break;
case MSG_UPDATE_SESSION_STATE:
// TODO add session state
break;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 310f3e9..4c475d9 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -30,14 +30,11 @@ import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
-import android.media.routeprovider.RouteRequest;
import android.media.session.IActiveSessionsListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
import android.media.session.MediaSession;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -74,15 +71,12 @@ public class MediaSessionService extends SystemService implements Monitor {
private static final int WAKELOCK_TIMEOUT = 5000;
private final SessionManagerImpl mSessionManagerImpl;
- // private final MediaRouteProviderWatcher mRouteProviderWatcher;
private final MediaSessionStack mPriorityStack;
private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
private final ArrayList<SessionsListenerRecord> mSessionsListeners
= new ArrayList<SessionsListenerRecord>();
- // private final ArrayList<MediaRouteProviderProxy> mProviders
- // = new ArrayList<MediaRouteProviderProxy>();
private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
@@ -94,10 +88,6 @@ public class MediaSessionService extends SystemService implements Monitor {
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
- // Used to keep track of the current request to show routes for a specific
- // session so we drop late callbacks properly.
- private int mShowRoutesRequestId = 0;
-
// Used to notify system UI when remote volume was changed. TODO find a
// better way to handle this.
private IRemoteVolumeController mRvc;
@@ -126,69 +116,6 @@ public class MediaSessionService extends SystemService implements Monitor {
return IAudioService.Stub.asInterface(b);
}
- /**
- * Should trigger showing the Media route picker dialog. Right now it just
- * kicks off a query to all the providers to get routes.
- *
- * @param record The session to show the picker for.
- */
- public void showRoutePickerForSession(MediaSessionRecord record) {
- // TODO for now just toggle the route to test (we will only have one
- // match for now)
- synchronized (mLock) {
- if (!mAllSessions.contains(record)) {
- Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
- return;
- }
- RouteInfo current = record.getRoute();
- UserRecord user = mUserRecords.get(record.getUserId());
- if (current != null) {
- // For now send null to mean the local route
- MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
- if (proxy != null) {
- proxy.removeSession(record);
- }
- record.selectRoute(null);
- return;
- }
- ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
- mShowRoutesRequestId++;
- for (int i = providers.size() - 1; i >= 0; i--) {
- MediaRouteProviderProxy provider = providers.get(i);
- provider.getRoutes(record, mShowRoutesRequestId);
- }
- }
- }
-
- /**
- * Connect a session to the given route.
- *
- * @param session The session to connect.
- * @param route The route to connect to.
- * @param options The options to use for the connection.
- */
- public void connectToRoute(MediaSessionRecord session, RouteInfo route,
- RouteOptions options) {
- synchronized (mLock) {
- if (!mAllSessions.contains(session)) {
- Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
- return;
- }
- UserRecord user = mUserRecords.get(session.getUserId());
- if (user == null) {
- Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
- return;
- }
- MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
- if (proxy == null) {
- Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
- return;
- }
- RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
- proxy.connectToRoute(session, route, request);
- }
- }
-
public void updateSession(MediaSessionRecord record) {
synchronized (mLock) {
if (!mAllSessions.contains(record)) {
@@ -552,110 +479,49 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- private MediaRouteProviderProxy.RoutesListener mRoutesCallback
- = new MediaRouteProviderProxy.RoutesListener() {
- @Override
- public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
- int reqId) {
- // TODO for now select the first route to test, eventually add the
- // new routes to the dialog if it is still open
- synchronized (mLock) {
- int index = findIndexOfSessionForIdLocked(sessionId);
- if (index != -1 && routes != null && routes.size() > 0) {
- MediaSessionRecord record = mAllSessions.get(index);
- RouteInfo route = routes.get(0);
- record.selectRoute(route);
- UserRecord user = mUserRecords.get(record.getUserId());
- MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
- provider.addSession(record);
- }
- }
- }
-
- @Override
- public void onRouteConnected(String sessionId, RouteInfo route,
- RouteRequest options, RouteConnectionRecord connection) {
- synchronized (mLock) {
- int index = findIndexOfSessionForIdLocked(sessionId);
- if (index != -1) {
- MediaSessionRecord session = mAllSessions.get(index);
- session.setRouteConnected(route, options.getConnectionOptions(), connection);
- }
- }
- }
- };
-
/**
* Information about a particular user. The contents of this object is
* guarded by mLock.
*/
final class UserRecord {
private final int mUserId;
- private final MediaRouteProviderWatcher mRouteProviderWatcher;
- private final ArrayList<MediaRouteProviderProxy> mProviders
- = new ArrayList<MediaRouteProviderProxy>();
private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
public UserRecord(Context context, int userId) {
mUserId = userId;
- mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
- mProviderWatcherCallback, mHandler, userId);
}
public void startLocked() {
- mRouteProviderWatcher.start();
+ // nothing for now
}
public void stopLocked() {
- mRouteProviderWatcher.stop();
- updateInterestLocked();
+ // nothing for now
}
public void destroyLocked() {
for (int i = mSessions.size() - 1; i >= 0; i--) {
MediaSessionRecord session = mSessions.get(i);
MediaSessionService.this.destroySessionLocked(session);
- if (session.isConnected()) {
- session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
- }
}
}
- public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
- return mProviders;
- }
-
public ArrayList<MediaSessionRecord> getSessionsLocked() {
return mSessions;
}
public void addSessionLocked(MediaSessionRecord session) {
mSessions.add(session);
- updateInterestLocked();
}
public void removeSessionLocked(MediaSessionRecord session) {
mSessions.remove(session);
- RouteInfo route = session.getRoute();
- if (route != null) {
- MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
- if (provider != null) {
- provider.removeSession(session);
- }
- }
- updateInterestLocked();
}
public void dumpLocked(PrintWriter pw, String prefix) {
pw.println(prefix + "Record for user " + mUserId);
String indent = prefix + " ";
- int size = mProviders.size();
- pw.println(indent + size + " Providers:");
- for (int i = 0; i < size; i++) {
- mProviders.get(i).dump(pw, indent);
- }
- pw.println();
- size = mSessions.size();
+ int size = mSessions.size();
pw.println(indent + size + " Sessions:");
for (int i = 0; i < size; i++) {
// Just print the session info, the full session dump will
@@ -663,48 +529,6 @@ public class MediaSessionService extends SystemService implements Monitor {
pw.println(indent + mSessions.get(i).getSessionInfo());
}
}
-
- public void updateInterestLocked() {
- // TODO go through the sessions and build up the set of interfaces
- // we're interested in. Update the provider watcher.
- // For now, just express interest in all providers for the current
- // user
- boolean interested = mUserId == mCurrentUserId;
- for (int i = mProviders.size() - 1; i >= 0; i--) {
- mProviders.get(i).setInterested(interested);
- }
- }
-
- private MediaRouteProviderProxy getProviderLocked(String providerId) {
- for (int i = mProviders.size() - 1; i >= 0; i--) {
- MediaRouteProviderProxy provider = mProviders.get(i);
- if (TextUtils.equals(providerId, provider.getId())) {
- return provider;
- }
- }
- return null;
- }
-
- private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
- = new MediaRouteProviderWatcher.Callback() {
- @Override
- public void removeProvider(MediaRouteProviderProxy provider) {
- synchronized (mLock) {
- mProviders.remove(provider);
- provider.setRoutesListener(null);
- provider.setInterested(false);
- }
- }
-
- @Override
- public void addProvider(MediaRouteProviderProxy provider) {
- synchronized (mLock) {
- mProviders.add(provider);
- provider.setRoutesListener(mRoutesCallback);
- provider.setInterested(true);
- }
- }
- };
}
final class SessionsListenerRecord implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index e26a2eb..3d1ecb3 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -25,7 +25,7 @@ import java.util.ArrayList;
/**
* Keeps track of media sessions and their priority for notifications, media
- * button routing, etc.
+ * button dispatch, etc.
*/
public class MediaSessionStack {
/**
@@ -277,8 +277,8 @@ public class MediaSessionStack {
lastActiveIndex++;
lastPublishedIndex++;
} else if (session.isPlaybackActive(true)) {
- // TODO replace getRoute() == null with real local route check
- if(session.getRoute() == null) {
+ // TODO this with real local route check
+ if (true) {
// Active local sessions get top priority
result.add(lastLocalIndex, session);
lastLocalIndex++;
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
deleted file mode 100644
index 90ddf29..0000000
--- a/services/core/java/com/android/server/media/RouteConnectionRecord.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 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.media.routeprovider.IRouteConnection;
-import android.media.session.RouteCommand;
-import android.media.session.RouteEvent;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-/**
- * A connection between a Session and a Route.
- */
-public class RouteConnectionRecord {
- private static final String TAG = "RouteConnRecord";
- private final IRouteConnection mBinder;
- private final String mPackageName;
- private final int mUid;
- private Listener mListener;
-
- public RouteConnectionRecord(IRouteConnection binder, String packageName, int uid) {
- mBinder = binder;
- mPackageName = packageName;
- mUid = uid;
- }
-
- /**
- * Add a listener to get route events on.
- *
- * @param listener The listener to get events on.
- */
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- /**
- * Check if this connection matches the token given.
- *
- * @param binder The token to check
- * @return True if this is the connection you're looking for, false
- * otherwise.
- */
- public boolean isConnection(IBinder binder) {
- return binder != null && binder.equals(mBinder.asBinder());
- }
-
- /**
- * Send an event from this connection.
- *
- * @param event The event to send.
- */
- public void sendEvent(RouteEvent event) {
- if (mListener != null) {
- mListener.onEvent(event);
- }
- }
-
- /**
- * Send a command to this connection.
- *
- * @param command The command to send.
- * @param cb The receiver to get a result on.
- */
- public void sendCommand(RouteCommand command, ResultReceiver cb) {
- try {
- mBinder.onCommand(command, cb);
- } catch (RemoteException e) {
- Log.e(TAG, "Error in sendCommand", e);
- }
- }
-
- /**
- * Tell the session that the provider has disconnected it.
- */
- public void disconnect() {
- if (mListener != null) {
- mListener.disconnect();
- }
- }
-
- @Override
- public String toString() {
- return "RouteConnection { binder=" + mBinder.toString() + ", package=" + mPackageName
- + ", uid=" + mUid + "}";
- }
-
- /**
- * Listener to receive updates from the provider for this connection.
- */
- public static interface Listener {
- /**
- * Called when an event is sent on this connection.
- *
- * @param event The event that was sent.
- */
- public void onEvent(RouteEvent event);
-
- /**
- * Called when the provider has disconnected the route.
- */
- public void disconnect();
- }
-} \ No newline at end of file
diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk
index 93b9c9a..4feac68 100644
--- a/tests/OneMedia/Android.mk
+++ b/tests/OneMedia/Android.mk
@@ -10,8 +10,7 @@ LOCAL_PACKAGE_NAME := OneMedia
LOCAL_CERTIFICATE := platform
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-v7-appcompat \
- android-support-v7-mediarouter
+ android-support-media-protocols
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 504d471..9d78ca5 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -27,11 +27,11 @@
android:process="com.android.onemedia.service" />
<service
android:name=".provider.OneMediaRouteProvider"
- android:permission="android.permission.BIND_ROUTE_PROVIDER"
+ android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE"
android:exported="true"
android:process="com.android.onemedia.provider">
<intent-filter>
- <action android:name="com.android.media.session.MediaRouteProvider" />
+ <action android:name="android.media.routing.MediaRouteService" />
</intent-filter>
</service>
</application>
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index ee407ad..894377b 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -28,8 +28,6 @@ import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
-import com.android.onemedia.playback.Renderer;
-
public class OnePlayerActivity extends Activity {
private static final String TAG = "OnePlayerActivity";
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 145b389..802f473 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -18,7 +18,6 @@ package com.android.onemedia;
import android.media.MediaMetadata;
import android.media.session.MediaController;
-import android.media.session.RouteInfo;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -121,7 +120,7 @@ public class PlayerController {
}
public void showRoutePicker() {
- mController.showRoutePicker();
+ // TODO
}
private void unbindFromService() {
@@ -173,11 +172,6 @@ public class PlayerController {
private class SessionCallback extends MediaController.Callback {
@Override
- public void onRouteChanged(RouteInfo route) {
- // TODO
- }
-
- @Override
public void onPlaybackStateChanged(PlaybackState state) {
if (state == null) {
return;
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index a220107..7c0eabe 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,14 +17,17 @@ package com.android.onemedia;
import android.content.Context;
import android.content.Intent;
-import android.media.session.Route;
-import android.media.session.RouteInfo;
-import android.media.session.RouteOptions;
-import android.media.session.RoutePlaybackControls;
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.MediaRouter;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.RouteInfo;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
import android.util.Log;
import android.view.KeyEvent;
@@ -34,11 +37,13 @@ import com.android.onemedia.playback.Renderer;
import com.android.onemedia.playback.RequestUtils;
import java.util.ArrayList;
+import java.util.List;
public class PlayerSession {
private static final String TAG = "PlayerSession";
protected MediaSession mSession;
+ protected MediaRouter mRouter;
protected Context mContext;
protected Renderer mRenderer;
protected MediaSession.Callback mCallback;
@@ -46,10 +51,6 @@ public class PlayerSession {
protected PlaybackState mPlaybackState;
protected Listener mListener;
- protected ArrayList<RouteOptions> mRouteOptions;
- protected Route mRoute;
- protected RoutePlaybackControls mRouteControls;
- protected RouteListener mRouteListener;
private String mContent;
@@ -63,41 +64,53 @@ public class PlayerSession {
| PlaybackState.ACTION_PLAY);
mRenderer.registerListener(mRenderListener);
-
- // TODO need an easier way to build route options
- mRouteOptions = new ArrayList<RouteOptions>();
- RouteOptions.Builder bob = new RouteOptions.Builder();
- bob.addInterface(RoutePlaybackControls.NAME);
- mRouteOptions.add(bob.build());
- mRouteListener = new RouteListener();
}
public void createSession() {
- if (mSession != null) {
- mSession.release();
- }
+ releaseSession();
+
MediaSessionManager man = (MediaSessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
+
+ mRouter = new MediaRouter(mContext);
+ mRouter.addSelector(new MediaRouteSelector.Builder()
+ .addRequiredProtocol(MediaPlayerProtocol.class)
+ .build());
+ mRouter.addSelector(new MediaRouteSelector.Builder()
+ .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO)
+ .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO)
+ .build());
+ mRouter.setRoutingCallback(new RoutingCallback(), null);
+
mSession = man.createSession("OneMedia");
mSession.addCallback(mCallback);
mSession.addTransportControlsCallback(new TransportCallback());
mSession.setPlaybackState(mPlaybackState);
mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mSession.setRouteOptions(mRouteOptions);
+ mSession.setMediaRouter(mRouter);
mSession.setActive(true);
}
public void onDestroy() {
- if (mSession != null) {
- mSession.release();
- }
+ releaseSession();
if (mRenderer != null) {
mRenderer.unregisterListener(mRenderListener);
mRenderer.onDestroy();
}
}
+ private void releaseSession() {
+ if (mSession != null) {
+ mSession.release();
+ mSession = null;
+ }
+ if (mRouter != null) {
+ mRouter.release();
+ mRouter = null;
+ }
+ }
+
public void setListener(Listener listener) {
mListener = listener;
}
@@ -218,40 +231,6 @@ public class PlayerSession {
}
}
}
-
- @Override
- public void onRequestRouteChange(RouteInfo route) {
- if (mRenderer != null) {
- mRenderer.onStop();
- }
- if (route == null) {
- // Use local route
- mRoute = null;
- mRenderer = new LocalRenderer(mContext, null);
- mRenderer.registerListener(mRenderListener);
- updateState(PlaybackState.STATE_NONE);
- } else {
- // Use remote route
- mSession.connect(route, mRouteOptions.get(0));
- mRenderer = null;
- updateState(PlaybackState.STATE_CONNECTING);
- }
- }
-
- @Override
- public void onRouteConnected(Route route) {
- mRoute = route;
- mRouteControls = RoutePlaybackControls.from(route);
- mRouteControls.addListener(mRouteListener);
- Log.d(TAG, "Connected to route, registering listener");
- mRenderer = new OneMRPRenderer(mRouteControls);
- updateState(PlaybackState.STATE_NONE);
- }
-
- @Override
- public void onRouteDisconnected(Route route, int reason) {
-
- }
}
private class TransportCallback extends MediaSession.TransportControlsCallback {
@@ -266,12 +245,62 @@ public class PlayerSession {
}
}
- private class RouteListener extends RoutePlaybackControls.Listener {
+ private class RoutingCallback extends MediaRouter.RoutingCallback {
@Override
- public void onPlaybackStateChange(int state) {
- Log.d(TAG, "Updating state to " + state);
- updateState(state);
+ public void onConnectionStateChanged(int state) {
+ if (state == MediaRouter.CONNECTION_STATE_CONNECTING) {
+ if (mRenderer != null) {
+ mRenderer.onStop();
+ }
+ mRenderer = null;
+ updateState(PlaybackState.STATE_CONNECTING);
+ return;
+ }
+
+ MediaRouter.ConnectionInfo connection = mRouter.getConnection();
+ if (connection != null) {
+ MediaPlayerProtocol protocol =
+ connection.getProtocolObject(MediaPlayerProtocol.class);
+ if (protocol != null) {
+ Log.d(TAG, "Connected to route using media player protocol");
+
+ protocol.setCallback(new PlayerCallback(), null);
+ mRenderer = new OneMRPRenderer(protocol);
+ updateState(PlaybackState.STATE_NONE);
+ return;
+ }
+ }
+
+ // Use local route
+ mRenderer = new LocalRenderer(mContext, null);
+ mRenderer.registerListener(mRenderListener);
+ updateState(PlaybackState.STATE_NONE);
}
}
+ private class PlayerCallback extends MediaPlayerProtocol.Callback {
+ @Override
+ public void onStatusUpdated(MediaStatus status, Bundle extras) {
+ if (status != null) {
+ Log.d(TAG, "Received status update: " + status.toBundle());
+ switch (status.getPlayerState()) {
+ case MediaStatus.PLAYER_STATE_BUFFERING:
+ updateState(PlaybackState.STATE_BUFFERING);
+ break;
+ case MediaStatus.PLAYER_STATE_IDLE:
+ updateState(PlaybackState.STATE_STOPPED);
+ break;
+ case MediaStatus.PLAYER_STATE_PAUSED:
+ updateState(PlaybackState.STATE_PAUSED);
+ break;
+ case MediaStatus.PLAYER_STATE_PLAYING:
+ updateState(PlaybackState.STATE_PLAYING);
+ break;
+ case MediaStatus.PLAYER_STATE_UNKNOWN:
+ updateState(PlaybackState.STATE_NONE);
+ break;
+ }
+ }
+ }
+ }
}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
index 05516d2..c133325 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -15,10 +15,10 @@
*/
package com.android.onemedia.playback;
+import android.media.MediaMetadata;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.support.v7.media.MediaItemMetadata;
/**
* TODO: Insert description here. (generated by epastern)
@@ -35,11 +35,11 @@ public class MediaItem implements Parcelable {
}
public String getTitle() {
- return mBundle.getString(MediaItemMetadata.KEY_TITLE);
+ return mBundle.getString(MediaMetadata.METADATA_KEY_TITLE);
}
public String getArtist() {
- return mBundle.getString(MediaItemMetadata.KEY_ALBUM_ARTIST);
+ return mBundle.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST);
}
/* (non-Javadoc)
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
index 9b0a2b2..55eb92c 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
@@ -1,39 +1,42 @@
package com.android.onemedia.playback;
-import android.media.session.RoutePlaybackControls;
import android.os.Bundle;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
/**
* Renderer for communicating with the OneMRP route
*/
public class OneMRPRenderer extends Renderer {
- private final RoutePlaybackControls mControls;
+ private final MediaPlayerProtocol mProtocol;
- public OneMRPRenderer(RoutePlaybackControls controls) {
+ public OneMRPRenderer(MediaPlayerProtocol protocol) {
super(null, null);
- mControls = controls;
+ mProtocol = protocol;
}
@Override
public void setContent(Bundle request) {
- mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
+ MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE),
+ MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3");
+ mProtocol.load(mediaInfo, true, 0, null);
}
@Override
public boolean onStop() {
- mControls.pause();
+ mProtocol.stop(null);
return true;
}
@Override
public boolean onPlay() {
- mControls.resume();
+ mProtocol.play(null);
return true;
}
@Override
public boolean onPause() {
- mControls.pause();
+ mProtocol.pause(null);
return true;
}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index dd0d982..3778c5f 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -16,7 +16,6 @@
package com.android.onemedia.playback;
import android.os.Bundle;
-import android.support.v7.media.MediaItemMetadata;
import java.util.HashMap;
import java.util.Map;
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
index f2d691c..2e1478b 100644
--- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -15,19 +15,20 @@
*/
package com.android.onemedia.provider;
-import android.media.routeprovider.RouteConnection;
-import android.media.routeprovider.RouteInterfaceHandler;
-import android.media.routeprovider.RoutePlaybackControlsHandler;
-import android.media.routeprovider.RouteProviderService;
-import android.media.routeprovider.RouteRequest;
-import android.media.session.RouteInfo;
-import android.media.session.RoutePlaybackControls;
-import android.media.session.RouteInterface;
+import android.media.routing.MediaRouteSelector;
+import android.media.routing.MediaRouteService;
+import android.media.routing.MediaRouter.ConnectionInfo;
+import android.media.routing.MediaRouter.ConnectionRequest;
+import android.media.routing.MediaRouter.DestinationInfo;
+import android.media.routing.MediaRouter.DiscoveryRequest;
+import android.media.routing.MediaRouter.RouteInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
+import android.os.Process;
+import android.support.media.protocols.MediaPlayerProtocol;
+import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
+import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
import android.util.Log;
import com.android.onemedia.playback.LocalRenderer;
@@ -35,29 +36,28 @@ import com.android.onemedia.playback.Renderer;
import com.android.onemedia.playback.RequestUtils;
import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
/**
* Test of MediaRouteProvider. Show a dummy provider with a simple interface for
* playing music.
*/
-public class OneMediaRouteProvider extends RouteProviderService {
+public class OneMediaRouteProvider extends MediaRouteService {
private static final String TAG = "OneMRP";
private static final boolean DEBUG = true;
+ private static final String TEST_DESTINATION_ID = "testDestination";
+ private static final String TEST_ROUTE_ID = "testRoute";
+
private Renderer mRenderer;
private RenderListener mRenderListener;
private PlaybackState mPlaybackState;
- private RouteConnection mConnection;
- private RoutePlaybackControlsHandler mControls;
- private String mRouteId;
private Handler mHandler;
+ private OneStub mStub;
+
@Override
public void onCreate() {
mHandler = new Handler();
- mRouteId = UUID.randomUUID().toString();
mRenderer = new LocalRenderer(this, null);
mRenderListener = new RenderListener();
mPlaybackState = new PlaybackState();
@@ -65,81 +65,106 @@ public class OneMediaRouteProvider extends RouteProviderService {
| PlaybackState.ACTION_PLAY);
mRenderer.registerListener(mRenderListener);
+ }
- if (DEBUG) {
- Log.d(TAG, "onCreate, routeId is " + mRouteId);
+ @Override
+ public ClientSession onCreateClientSession(ClientInfo client) {
+ if (client.getUid() != Process.myUid()) {
+ // for testing purposes, only allow connections from this application
+ // since this provider is not fully featured
+ return null;
}
+ return new OneSession(client);
}
- @Override
- public List<RouteInfo> getMatchingRoutes(List<RouteRequest> requests) {
- RouteInfo.Builder bob = new RouteInfo.Builder();
- bob.setName("OneMedia").setId(mRouteId);
- // TODO add a helper library for generating route info with the correct
- // options
- Log.d(TAG, "Requests:");
- for (RouteRequest request : requests) {
- List<String> ifaces = request.getConnectionOptions().getInterfaceNames();
- Log.d(TAG, " request ifaces:" + ifaces.toString());
- if (ifaces != null && ifaces.size() == 1
- && RoutePlaybackControls.NAME.equals(ifaces.get(0))) {
- bob.addRouteOptions(request.getConnectionOptions());
+ private final class OneSession extends ClientSession {
+ private final ClientInfo mClient;
+
+ public OneSession(ClientInfo client) {
+ mClient = client;
+ }
+
+ @Override
+ public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) {
+ for (MediaRouteSelector selector : req.getSelectors()) {
+ if (isMatch(selector)) {
+ DestinationInfo destination = new DestinationInfo.Builder(
+ TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia")
+ .setDescription("Test route from OneMedia app.")
+ .build();
+ ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
+ routes.add(new RouteInfo.Builder(
+ TEST_ROUTE_ID, destination, selector).build());
+ callback.onDestinationFound(destination, routes);
+ return true;
+ }
}
+ return false;
}
- ArrayList<RouteInfo> result = new ArrayList<RouteInfo>();
- if (bob.getOptionsSize() > 0) {
- RouteInfo info = bob.build();
- result.add(info);
+
+ @Override
+ public void onStopDiscovery() {
}
- if (DEBUG) {
- Log.d(TAG, "getRoutes returning " + result.toString());
+
+ @Override
+ public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) {
+ if (req.getRoute().getId().equals(TEST_ROUTE_ID)) {
+ mStub = new OneStub();
+ ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute())
+ .setProtocolStub(MediaPlayerProtocol.class, mStub)
+ .build();
+ callback.onConnected(connection);
+ return true;
+ }
+ return false;
}
- return result;
- }
- @Override
- public RouteConnection connect(RouteInfo route, RouteRequest request) {
- if (mConnection != null) {
- disconnect(mConnection);
+ @Override
+ public void onDisconnect() {
+ mStub = null;
}
- RouteConnection connection = new RouteConnection(this, route);
- mControls = RoutePlaybackControlsHandler.addTo(connection);
- mControls.addListener(new PlayHandler(mRouteId), mHandler);
- if (DEBUG) {
- Log.d(TAG, "Connected to route");
+
+ private boolean isMatch(MediaRouteSelector selector) {
+ if (!selector.containsProtocol(MediaPlayerProtocol.class)) {
+ return false;
+ }
+ for (String protocol : selector.getRequiredProtocols()) {
+ if (!protocol.equals(MediaPlayerProtocol.class.getName())) {
+ return false;
+ }
+ }
+ return true;
}
- return connection;
}
- private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
- private final String mRouteId;
+ private final class OneStub extends MediaPlayerProtocol.Stub {
+ MediaInfo mMediaInfo;
- public PlayHandler(String routeId) {
- mRouteId = routeId;
+ public OneStub() {
+ super(mHandler);
}
@Override
- public void playNow(String content, ResultReceiver cb) {
+ public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition,
+ Bundle extras) {
if (DEBUG) {
- Log.d(TAG, "Attempting to play " + content);
+ Log.d(TAG, "Attempting to play " + mediaInfo.getContentId());
}
// look up the route and send a play command to it
+ mMediaInfo = mediaInfo;
Bundle bundle = new Bundle();
- bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
+ bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId());
mRenderer.setContent(bundle);
- RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
}
@Override
- public boolean resume() {
+ public void onPlay(Bundle extras) {
mRenderer.onPlay();
- return true;
}
@Override
- public boolean pause() {
+ public void onPause(Bundle extras) {
mRenderer.onPause();
- return true;
}
}
@@ -148,9 +173,7 @@ public class OneMediaRouteProvider extends RouteProviderService {
@Override
public void onError(int type, int extra, Bundle extras, Throwable error) {
Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
- if (mControls != null) {
- mControls.sendPlaybackChangeEvent(PlaybackState.STATE_ERROR);
- }
+ sendStatusUpdate(PlaybackState.STATE_ERROR);
}
@Override
@@ -186,7 +209,7 @@ public class OneMediaRouteProvider extends RouteProviderService {
break;
}
- mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+ sendStatusUpdate(mPlaybackState.getState());
}
@Override
@@ -203,5 +226,36 @@ public class OneMediaRouteProvider extends RouteProviderService {
@Override
public void onNextStarted() {
}
+
+ private void sendStatusUpdate(int state) {
+ if (mStub != null) {
+ MediaStatus status = new MediaStatus(1, mStub.mMediaInfo);
+ switch (state) {
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING);
+ break;
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_STOPPED:
+ status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE);
+ break;
+ case PlaybackState.STATE_PAUSED:
+ status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED);
+ break;
+ case PlaybackState.STATE_PLAYING:
+ status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING);
+ break;
+ case PlaybackState.STATE_NONE:
+ case PlaybackState.STATE_ERROR:
+ default:
+ status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN);
+ break;
+ }
+ mStub.sendStatusUpdatedEvent(status, null);
+ }
+ }
}
}