Merge "Add TRANSCRIPTION column to Calls table" into lmp-dev
diff --git a/Android.mk b/Android.mk
index 23c4753..96b2b17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -320,9 +320,12 @@
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 @@
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 588d558..ea21786 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23,6 +23,7 @@
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";
@@ -11857,7 +11858,7 @@
package android.hardware {
- public class Camera {
+ public deprecated class Camera {
method public final void addCallbackBuffer(byte[]);
method public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
method public final void cancelAutoFocus();
@@ -11896,21 +11897,21 @@
field public static final int CAMERA_ERROR_UNKNOWN = 1; // 0x1
}
- public static class Camera.Area {
+ public static deprecated class Camera.Area {
ctor public Camera.Area(android.graphics.Rect, int);
field public android.graphics.Rect rect;
field public int weight;
}
- public static abstract interface Camera.AutoFocusCallback {
+ public static abstract deprecated interface Camera.AutoFocusCallback {
method public abstract void onAutoFocus(boolean, android.hardware.Camera);
}
- public static abstract interface Camera.AutoFocusMoveCallback {
+ public static abstract deprecated interface Camera.AutoFocusMoveCallback {
method public abstract void onAutoFocusMoving(boolean, android.hardware.Camera);
}
- public static class Camera.CameraInfo {
+ public static deprecated class Camera.CameraInfo {
ctor public Camera.CameraInfo();
field public static final int CAMERA_FACING_BACK = 0; // 0x0
field public static final int CAMERA_FACING_FRONT = 1; // 0x1
@@ -11919,11 +11920,11 @@
field public int orientation;
}
- public static abstract interface Camera.ErrorCallback {
+ public static abstract deprecated interface Camera.ErrorCallback {
method public abstract void onError(int, android.hardware.Camera);
}
- public static class Camera.Face {
+ public static deprecated class Camera.Face {
ctor public Camera.Face();
field public int id;
field public android.graphics.Point leftEye;
@@ -11933,15 +11934,15 @@
field public int score;
}
- public static abstract interface Camera.FaceDetectionListener {
+ public static abstract deprecated interface Camera.FaceDetectionListener {
method public abstract void onFaceDetection(android.hardware.Camera.Face[], android.hardware.Camera);
}
- public static abstract interface Camera.OnZoomChangeListener {
+ public static abstract deprecated interface Camera.OnZoomChangeListener {
method public abstract void onZoomChange(int, boolean, android.hardware.Camera);
}
- public class Camera.Parameters {
+ public deprecated class Camera.Parameters {
method public java.lang.String flatten();
method public java.lang.String get(java.lang.String);
method public java.lang.String getAntibanding();
@@ -12091,19 +12092,19 @@
field public static final java.lang.String WHITE_BALANCE_WARM_FLUORESCENT = "warm-fluorescent";
}
- public static abstract interface Camera.PictureCallback {
+ public static abstract deprecated interface Camera.PictureCallback {
method public abstract void onPictureTaken(byte[], android.hardware.Camera);
}
- public static abstract interface Camera.PreviewCallback {
+ public static abstract deprecated interface Camera.PreviewCallback {
method public abstract void onPreviewFrame(byte[], android.hardware.Camera);
}
- public static abstract interface Camera.ShutterCallback {
+ public static abstract deprecated interface Camera.ShutterCallback {
method public abstract void onShutter();
}
- public class Camera.Size {
+ public deprecated class Camera.Size {
ctor public Camera.Size(int, int);
field public int height;
field public int width;
@@ -14970,7 +14971,7 @@
method public void setAudioEncodingBitRate(int);
method public void setAudioSamplingRate(int);
method public void setAudioSource(int) throws java.lang.IllegalStateException;
- method public void setCamera(android.hardware.Camera);
+ method public deprecated void setCamera(android.hardware.Camera);
method public void setCaptureRate(double);
method public void setLocation(float, float);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
@@ -15871,12 +15872,249 @@
}
+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 @@
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 @@
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
@@ -24659,6 +24899,7 @@
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.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 @@
}
@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/app/Activity.java b/core/java/android/app/Activity.java
index f1a2576..d20a5dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3751,6 +3751,48 @@
/**
* @hide Implement to provide correct calling token.
*/
+ public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) {
+ startActivityForResultAsUser(intent, requestCode, null, user);
+ }
+
+ /**
+ * @hide Implement to provide correct calling token.
+ */
+ public void startActivityForResultAsUser(Intent intent, int requestCode,
+ @Nullable Bundle options, UserHandle user) {
+ if (options != null) {
+ mActivityTransitionState.startExitOutTransition(this, options);
+ }
+ if (mParent != null) {
+ throw new RuntimeException("Can't be called from a child");
+ }
+ Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode,
+ options, user);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+ }
+ if (requestCode >= 0) {
+ // If this start is requesting a result, we can avoid making
+ // the activity visible until the result is received. Setting
+ // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+ // activity hidden during this time, to avoid flickering.
+ // This can only be done when a result is requested because
+ // that guarantees we will get information back when the
+ // activity is finished, no matter what happens to it.
+ mStartedActivity = true;
+ }
+
+ final View decor = mWindow != null ? mWindow.peekDecorView() : null;
+ if (decor != null) {
+ decor.cancelPendingInputEvents();
+ }
+ }
+
+ /**
+ * @hide Implement to provide correct calling token.
+ */
public void startActivityAsUser(Intent intent, UserHandle user) {
startActivityAsUser(intent, null, user);
}
@@ -3760,7 +3802,7 @@
*/
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
if (mParent != null) {
- throw new RuntimeException("Called be called from a child");
+ throw new RuntimeException("Can't be called from a child");
}
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index cf462cd..4c73e6a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -133,7 +133,11 @@
* <p>For more information about using cameras, read the
* <a href="{@docRoot}guide/topics/media/camera.html">Camera</a> developer guide.</p>
* </div>
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+@Deprecated
public class Camera {
private static final String TAG = "Camera";
@@ -247,7 +251,11 @@
/**
* Information about a camera
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public static class CameraInfo {
/**
* The facing of the camera is opposite to that of the screen.
@@ -675,7 +683,11 @@
* @see #setOneShotPreviewCallback(Camera.PreviewCallback)
* @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback)
* @see #startPreview()
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface PreviewCallback
{
/**
@@ -1175,7 +1187,10 @@
* manifest element.</p>
*
* @see #autoFocus(AutoFocusCallback)
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface AutoFocusCallback
{
/**
@@ -1286,7 +1301,11 @@
* Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
* Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
* autofocus animation based on this.</p>
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface AutoFocusMoveCallback
{
/**
@@ -1314,7 +1333,11 @@
* Callback interface used to signal the moment of actual image capture.
*
* @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface ShutterCallback
{
/**
@@ -1331,7 +1354,11 @@
* Callback interface used to supply image data from a photo capture.
*
* @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface PictureCallback {
/**
* Called when image data is available after a picture is taken.
@@ -1538,7 +1565,11 @@
*
* @see #setZoomChangeListener(OnZoomChangeListener)
* @see #startSmoothZoom(int)
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface OnZoomChangeListener
{
/**
@@ -1568,7 +1599,10 @@
/**
* Callback interface for face detected in the preview frame.
*
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface FaceDetectionListener
{
/**
@@ -1652,7 +1686,10 @@
* list of face objects for use in focusing and metering.</p>
*
* @see FaceDetectionListener
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public static class Face {
/**
* Create an empty face.
@@ -1766,7 +1803,11 @@
* Callback interface for camera error notification.
*
* @see #setErrorCallback(ErrorCallback)
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public interface ErrorCallback
{
/**
@@ -1864,7 +1905,10 @@
/**
* Image size (width and height dimensions).
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public class Size {
/**
* Sets the dimensions for pictures.
@@ -1931,7 +1975,11 @@
* @see Parameters#setMeteringAreas(List)
* @see Parameters#getMeteringAreas()
* @see Parameters#getMaxNumMeteringAreas()
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public static class Area {
/**
* Create an area with specified rectangle and weight.
@@ -2005,7 +2053,11 @@
* calling {@link Camera.Parameters#setColorEffect(String)}. If the
* camera does not support color effects,
* {@link Camera.Parameters#getSupportedColorEffects()} will return null.
+ *
+ * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+ * applications.
*/
+ @Deprecated
public class Parameters {
// Parameter keys to communicate with the camera driver.
private static final String KEY_PREVIEW_SIZE = "preview-size";
@@ -2960,7 +3012,6 @@
case ImageFormat.YV12: return PIXEL_FORMAT_YUV420P;
case ImageFormat.RGB_565: return PIXEL_FORMAT_RGB565;
case ImageFormat.JPEG: return PIXEL_FORMAT_JPEG;
- case ImageFormat.BAYER_RGGB: return PIXEL_FORMAT_BAYER_RGGB;
default: return null;
}
}
diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html
index ef0d7bd..719c2f6 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -58,16 +58,16 @@
<p>Generally, camera preview images are sent to {@link
android.view.SurfaceView} or {@link android.view.TextureView} (via its
{@link android.graphics.SurfaceTexture}). Capture of JPEG images or
-RAW buffers for {@link android.hardware.camera2.DngCreator} can be done
-with {@link android.media.ImageReader} with the
-{android.graphics.ImageFormat#JPEG} and
-{android.graphics.ImageFormat#RAW_SENSOR} formats. Application-driven
+RAW buffers for {@link android.hardware.camera2.DngCreator} can be
+done with {@link android.media.ImageReader} with the {@link
+android.graphics.ImageFormat#JPEG} and {@link
+android.graphics.ImageFormat#RAW_SENSOR} formats. Application-driven
processing of camera data in RenderScript, OpenGL ES, or directly in
managed or native code is best done through {@link
android.renderscript.Allocation} with a YUV {@link
android.renderscript.Type}, {@link android.graphics.SurfaceTexture},
-and {@link android.media.ImageReader} with a
-{android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
+and {@link android.media.ImageReader} with a {@link
+android.graphics.ImageFormat#YUV_420_888} format, respectively.</p>
<p>The application then needs to construct a {@link
android.hardware.camera2.CaptureRequest}, which defines all the
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 @@
"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/docs/html/guide/topics/media/camera.jd b/docs/html/guide/topics/media/camera.jd
index 56ef624..8b79b23 100644
--- a/docs/html/guide/topics/media/camera.jd
+++ b/docs/html/guide/topics/media/camera.jd
@@ -86,14 +86,17 @@
<h2 id="basics">The Basics</h2>
<p>The Android framework supports capturing images and video through the
-{@link android.hardware.Camera} API or camera {@link android.content.Intent}. Here are the relevant
+{@link android.hardware.camera2} API or camera {@link android.content.Intent}. Here are the relevant
classes:</p>
<dl>
- <dt>{@link android.hardware.Camera}</dt>
- <dd>This class is the primary API for controlling device cameras. This class is used to take
+ <dt>{@link android.hardware.camera2}</dt>
+ <dd>This package is the primary API for controlling device cameras. It can be used to take
pictures or videos when you are building a camera application.</dd>
+ <dt>{@link android.hardware.Camera}</dt>
+ <dd>This class is the older deprecated API for controlling device cameras.</dd>
+
<dt>{@link android.view.SurfaceView}</dt>
<dd>This class is used to present a live camera preview to the user.</dd>
@@ -354,6 +357,10 @@
code than <a href="#intents">using an intent</a>, but it can provide a more compelling experience
for your users.</p>
+<p><strong> Note: The following guide is for the older, deprecated {@link android.hardware.Camera}
+API. For new or advanced camera applications, the newer {@link android.hardware.camera2} API is
+recommended.</strong></p>
+
<p>The general steps for creating a custom camera interface for your application are as follows:</p>
<ul>
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 28fd7ba..ab4258d 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -54,9 +54,12 @@
* cr_offset = y_size
* cb_offset = y_size + c_size</pre>
*
- * <p>This format is guaranteed to be supported for camera preview images since
- * API level 12; for earlier API versions, check
- * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+ * recommended for YUV output instead.</p>
+ *
+ * <p>For the older camera API, this format is guaranteed to be supported for
+ * {@link android.hardware.Camera} preview images since API level 12; for earlier API versions,
+ * check {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
*
* <p>Note that for camera preview callback use (see
* {@link android.hardware.Camera#setPreviewCallback}), the
@@ -133,29 +136,47 @@
public static final int Y16 = 0x20363159;
/**
- * YCbCr format, used for video. Whether this format is supported by the
- * camera hardware can be determined by
- * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ * YCbCr format, used for video.
+ *
+ * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+ * recommended for YUV output instead.</p>
+ *
+ * <p>Whether this format is supported by the old camera API can be determined by
+ * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
+ *
*/
public static final int NV16 = 0x10;
/**
- * YCrCb format used for images, which uses the NV21 encoding format. This
- * is the default format for camera preview images, when not otherwise set
- * with {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.
+ * YCrCb format used for images, which uses the NV21 encoding format.
+ *
+ * <p>This is the default format
+ * for {@link android.hardware.Camera} preview images, when not otherwise set with
+ * {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}.</p>
+ *
+ * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+ * recommended for YUV output instead.</p>
*/
public static final int NV21 = 0x11;
/**
* YCbCr format used for images, which uses YUYV (YUY2) encoding format.
- * This is an alternative format for camera preview images. Whether this
- * format is supported by the camera hardware can be determined by
- * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ *
+ * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
+ * recommended for YUV output instead.</p>
+ *
+ * <p>This is an alternative format for {@link android.hardware.Camera} preview images. Whether
+ * this format is supported by the camera hardware can be determined by
+ * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.</p>
*/
public static final int YUY2 = 0x14;
/**
- * Encoded formats. These are not necessarily supported by the hardware.
+ * Compressed JPEG format.
+ *
+ * <p>This format is always supported as an output format for the
+ * {@link android.hardware.camera2} API, and as a picture format for the older
+ * {@link android.hardware.Camera} API</p>
*/
public static final int JPEG = 0x100;
@@ -332,16 +353,6 @@
public static final int RAW10 = 0x25;
/**
- * Raw bayer format used for images, which is 10 bit precision samples
- * stored in 16 bit words. The filter pattern is RGGB. Whether this format
- * is supported by the camera hardware can be determined by
- * {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
- *
- * @hide
- */
- public static final int BAYER_RGGB = 0x200;
-
- /**
* Use this function to retrieve the number of bits per pixel of an
* ImageFormat.
*
@@ -369,8 +380,6 @@
return 12;
case RAW_SENSOR:
return 16;
- case BAYER_RGGB:
- return 16;
case RAW10:
return 10;
}
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 17795f3..f52c661 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -27,14 +27,18 @@
/**
* Captures frames from an image stream as an OpenGL ES texture.
*
- * <p>The image stream may come from either camera preview or video decode. A SurfaceTexture
- * may be used in place of a SurfaceHolder when specifying the output destination of a
- * {@link android.hardware.Camera} or {@link android.media.MediaPlayer}
- * object. Doing so will cause all the frames from the image stream to be sent to the
- * SurfaceTexture object rather than to the device's display. When {@link #updateTexImage} is
- * called, the contents of the texture object specified when the SurfaceTexture was created are
- * updated to contain the most recent image from the image stream. This may cause some frames of
- * the stream to be skipped.
+ * <p>The image stream may come from either camera preview or video decode. A
+ * {@link android.view.Surface} created from a SurfaceTexture can be used as an output
+ * destination for the {@link android.hardware.camera2}, {@link android.media.MediaCodec},
+ * {@link android.media.MediaPlayer}, and {@link android.renderscript.Allocation} APIs.
+ * When {@link #updateTexImage} is called, the contents of the texture object specified
+ * when the SurfaceTexture was created are updated to contain the most recent image from the image
+ * stream. This may cause some frames of the stream to be skipped.
+ *
+ * <p>A SurfaceTexture may also be used in place of a SurfaceHolder when specifying the output
+ * destination of the older {@link android.hardware.Camera} API. Doing so will cause all the
+ * frames from the image stream to be sent to the SurfaceTexture object rather than to the device's
+ * display.
*
* <p>When sampling from the texture one should first transform the texture coordinates using the
* matrix queried via {@link #getTransformMatrix(float[])}. The transform matrix may change each
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index eb6bf2c..c65961d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2912,6 +2912,20 @@
}
/**
+ * Notify audio manager about volume controller visibility changes.
+ * Currently limited to SystemUI.
+ *
+ * @hide
+ */
+ public void notifyVolumeControllerVisible(IVolumeController controller, boolean visible) {
+ try {
+ getService().notifyVolumeControllerVisible(controller, visible);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error notifying about volume controller visibility", e);
+ }
+ }
+
+ /**
* Only useful for volume controllers.
* @hide
*/
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index b08d631..ab63145 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -61,6 +61,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -810,6 +811,9 @@
// Restore the default media button receiver from the system settings
mMediaFocusControl.restoreMediaButtonReceiver();
+
+ // Load settings for the volume controller
+ mVolumeController.loadSettings(cr);
}
private int rescaleIndex(int index, int srcStream, int dstStream) {
@@ -851,14 +855,23 @@
} else {
streamType = getActiveStreamType(suggestedStreamType);
}
+ final int resolvedStream = mStreamVolumeAlias[streamType];
// Play sounds on STREAM_RING and STREAM_REMOTE_MUSIC only.
if ((streamType != STREAM_REMOTE_MUSIC) &&
(flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
- (mStreamVolumeAlias[streamType] != AudioSystem.STREAM_RING)) {
+ resolvedStream != AudioSystem.STREAM_RING) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
+ // For notifications/ring, show the ui before making any adjustments
+ if (mVolumeController.suppressAdjustment(resolvedStream, flags)) {
+ direction = 0;
+ flags &= ~AudioManager.FLAG_PLAY_SOUND;
+ flags &= ~AudioManager.FLAG_VIBRATE;
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
+ }
+
if (streamType == STREAM_REMOTE_MUSIC) {
// TODO bounce it to MediaSessionService to find an appropriate
// session
@@ -4955,15 +4968,65 @@
}
}
mVolumeController.setController(controller);
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+ }
+
+ @Override
+ public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
+ enforceSelfOrSystemUI("notify about volume controller visibility");
+
+ // return early if the controller is not current
+ if (!mVolumeController.isSameBinder(controller)) {
+ return;
+ }
+
+ mVolumeController.setVisible(visible);
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible);
}
public static class VolumeController {
private static final String TAG = "VolumeController";
private IVolumeController mController;
+ private boolean mVisible;
+ private long mNextLongPress;
+ private int mLongPressTimeout;
public void setController(IVolumeController controller) {
mController = controller;
+ mVisible = false;
+ }
+
+ public void loadSettings(ContentResolver cr) {
+ mLongPressTimeout = Settings.Secure.getIntForUser(cr,
+ Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
+ }
+
+ public boolean suppressAdjustment(int resolvedStream, int flags) {
+ boolean suppress = false;
+ if (resolvedStream == AudioSystem.STREAM_RING && mController != null) {
+ final long now = SystemClock.uptimeMillis();
+ if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
+ // ui will become visible
+ if (mNextLongPress < now) {
+ mNextLongPress = now + mLongPressTimeout;
+ }
+ suppress = true;
+ } else if (mNextLongPress > 0) { // in a long-press
+ if (now > mNextLongPress) {
+ // long press triggered, no more suppression
+ mNextLongPress = 0;
+ } else {
+ // keep suppressing until the long press triggers
+ suppress = true;
+ }
+ }
+ }
+ return suppress;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
}
public boolean isSameBinder(IVolumeController controller) {
@@ -4980,7 +5043,7 @@
@Override
public String toString() {
- return "VolumeController(" + asBinder() + ")";
+ return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
}
public void postDisplaySafeVolumeWarning(int flags) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index e112a65..4f7021e 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -195,6 +195,8 @@
void setVolumeController(in IVolumeController controller);
+ void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
+
boolean isStreamAffectedByRingerMode(int streamType);
void disableSafeMediaVolume();
diff --git a/media/java/android/media/MediaActionSound.java b/media/java/android/media/MediaActionSound.java
index 7a520fe..2f4d136 100644
--- a/media/java/android/media/MediaActionSound.java
+++ b/media/java/android/media/MediaActionSound.java
@@ -24,13 +24,15 @@
* <p>A class for producing sounds that match those produced by various actions
* taken by the media and camera APIs. </p>
*
- * <p>Use this class to play an appropriate camera operation sound when
- * implementing a custom still or video recording mechanism (through the Camera
- * preview callbacks with {@link android.hardware.Camera#setPreviewCallback
- * Camera.setPreviewCallback}, or through GPU processing with {@link
- * android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
- * example), or when implementing some other camera-like function in your
- * application.</p>
+ * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the
+ * camera2 API does not play any sounds on its own for any capture or video recording actions.</p>
+ *
+ * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate
+ * camera operation sound when implementing a custom still or video recording mechanism (through the
+ * Camera preview callbacks with
+ * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU
+ * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
+ * example), or when implementing some other camera-like function in your application.</p>
*
* <p>There is no need to play sounds when using
* {@link android.hardware.Camera#takePicture Camera.takePicture} or
@@ -136,10 +138,12 @@
* {@link android.media.MediaRecorder#start MediaRecorder.start}, and
* {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
*
- * <p>Using this method makes it easy to match the default device sounds
- * when recording or capturing data through the preview callbacks, or when
- * implementing custom camera-like features in your
- * application.</p>
+ * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
+ * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
+
+ * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
+ * match the default device sounds when recording or capturing data through the preview
+ * callbacks, or when implementing custom camera-like features in your application.</p>
*
* <p>If the sound has not been loaded by {@link #load} before calling play,
* play will load the sound at the cost of some additional latency before
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f8c0494..3917bf1 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -114,23 +114,26 @@
}
/**
- * Sets a Camera to use for recording. Use this function to switch
- * quickly between preview and capture mode without a teardown of
- * the camera object. {@link android.hardware.Camera#unlock()} should be
- * called before this. Must call before prepare().
+ * Sets a {@link android.hardware.Camera} to use for recording.
+ *
+ * <p>Use this function to switch quickly between preview and capture mode without a teardown of
+ * the camera object. {@link android.hardware.Camera#unlock()} should be called before
+ * this. Must call before {@link #prepare}.</p>
*
* @param c the Camera to use for recording
+ * @deprecated Use {@link #getSurface} and the {@link android.hardware.camera2} API instead.
*/
+ @Deprecated
public native void setCamera(Camera c);
/**
* Gets the surface to record from when using SURFACE video source.
- * <p>
- * Should only be called after prepare(). Frames rendered before start()
- * will be discarded.
- * </p>
- * @throws IllegalStateException if it is called before prepare(), after
- * stop() or is called when VideoSource is not set to SURFACE.
+ *
+ * <p> May only be called after {@link #prepare}. Frames rendered to the Surface before
+ * {@link #start} will be discarded.</p>
+ *
+ * @throws IllegalStateException if it is called before {@link #prepare}, after
+ * {@link #stop}, or is called when VideoSource is not set to SURFACE.
* @see android.media.MediaRecorder.VideoSource
*/
public native Surface getSurface();
@@ -239,7 +242,7 @@
public static final int DEFAULT = 0;
/** Camera video source
* <p>
- * Using android.hardware.Camera as video source.
+ * Using the {@link android.hardware.Camera} API as video source.
* </p>
*/
public static final int CAMERA = 1;
@@ -248,7 +251,7 @@
* Using a Surface as video source.
* </p><p>
* This flag must be used when recording from an
- * android.hardware.camera2.CameraDevice source.
+ * {@link android.hardware.camera2} API source.
* </p><p>
* When using this video source type, use {@link MediaRecorder#getSurface()}
* to retrieve the surface created by MediaRecorder.
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/IRouteProviderCallback.aidl b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
deleted file mode 100644
index 9185347..0000000
--- a/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
+++ /dev/null
@@ -1,32 +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.RouteEvent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-/**
- * 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/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>
- * <service android:name=".MyRouteProviderService"
- * android:label="@string/my_route_provider_service">
- * <intent-filter>
- * <action android:name="com.android.media.session.MediaRouteProvider" />
- * </intent-filter>
- * </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.aidl b/media/java/android/media/routeprovider/RouteRequest.aidl
deleted file mode 100644
index 7bc5722..0000000
--- a/media/java/android/media/routeprovider/RouteRequest.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.routeprovider;
-
-parcelable RouteRequest;
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
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to 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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterDelegate.aidl
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to media/java/android/media/routing/IMediaRouterDelegate.aidl
index 15c8039..35f84c8 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterDelegate.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 IMediaRouterDelegate {
-/**
- * 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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
similarity index 66%
rename from media/java/android/media/routeprovider/IRouteConnection.aidl
rename to media/java/android/media/routing/IMediaRouterRoutingCallback.aidl
index 15c8039..173ae55 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterRoutingCallback.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 IMediaRouterRoutingCallback {
-/**
- * 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/IRouteConnection.aidl b/media/java/android/media/routing/IMediaRouterStateCallback.aidl
similarity index 66%
copy from media/java/android/media/routeprovider/IRouteConnection.aidl
copy to media/java/android/media/routing/IMediaRouterStateCallback.aidl
index 15c8039..0299904 100644
--- a/media/java/android/media/routeprovider/IRouteConnection.aidl
+++ b/media/java/android/media/routing/IMediaRouterStateCallback.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 IMediaRouterStateCallback {
-/**
- * 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/session/RouteCommand.aidl b/media/java/android/media/routing/MediaRouteSelector.aidl
similarity index 90%
copy from media/java/android/media/session/RouteCommand.aidl
copy to media/java/android/media/routing/MediaRouteSelector.aidl
index 725b308..37bfa4a 100644
--- a/media/java/android/media/session/RouteCommand.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 RouteCommand;
+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>
+ * <service android:name=".MediaRouteProvider"
+ * android:label="@string/service_name"
+ * android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.media.routing.MediaRouteService" />
+ * </intent-filter>
+ *
+ * TODO: INSERT METADATA DECLARATIONS HERE
+ *
+ * </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/RouteCommand.aidl b/media/java/android/media/routing/ParcelableConnectionInfo.aidl
similarity index 89%
copy from media/java/android/media/session/RouteCommand.aidl
copy to media/java/android/media/routing/ParcelableConnectionInfo.aidl
index 725b308..4a9ec94 100644
--- a/media/java/android/media/session/RouteCommand.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 RouteCommand;
+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
similarity index 89%
copy from media/java/android/media/session/RouteCommand.aidl
copy to 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/session/RouteCommand.aidl b/media/java/android/media/routing/ParcelableRouteInfo.aidl
similarity index 90%
rename from media/java/android/media/session/RouteCommand.aidl
rename to media/java/android/media/routing/ParcelableRouteInfo.aidl
index 725b308..126afaa 100644
--- a/media/java/android/media/session/RouteCommand.aidl
+++ b/media/java/android/media/routing/ParcelableRouteInfo.aidl
@@ -13,6 +13,6 @@
** limitations under the License.
*/
-package android.media.session;
+package android.media.routing;
-parcelable RouteCommand;
+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 @@
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 @@
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 @@
// 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 0316d1fa..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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
*/
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.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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
@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 @@
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.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 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 @@
= 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 @@
}
/**
+ * 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 @@
}
/**
- * 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 @@
}
}
- 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 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 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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@Override
- public void onPause() throws RemoteException {
+ public void onPause() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPause();
@@ -990,7 +781,7 @@
}
@Override
- public void onStop() throws RemoteException {
+ public void onStop() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchStop();
@@ -998,7 +789,7 @@
}
@Override
- public void onNext() throws RemoteException {
+ public void onNext() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchNext();
@@ -1006,7 +797,7 @@
}
@Override
- public void onPrevious() throws RemoteException {
+ public void onPrevious() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchPrevious();
@@ -1014,7 +805,7 @@
}
@Override
- public void onFastForward() throws RemoteException {
+ public void onFastForward() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchFastForward();
@@ -1022,7 +813,7 @@
}
@Override
- public void onRewind() throws RemoteException {
+ public void onRewind() {
MediaSession session = mMediaSession.get();
if (session != null) {
session.dispatchRewind();
@@ -1030,7 +821,7 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
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/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 5d1a7e02..11f7720 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -202,7 +202,7 @@
if (up) {
flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
} else {
- flags = AudioManager.FLAG_SHOW_UI;
+ flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
}
}
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.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 @@
}
/**
- * 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 @@
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 @@
/**
* 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.aidl b/media/java/android/media/session/RouteEvent.aidl
deleted file mode 100644
index 6966207..0000000
--- a/media/java/android/media/session/RouteEvent.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 RouteEvent;
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.aidl b/media/java/android/media/session/RouteInfo.aidl
deleted file mode 100644
index c5f50c8..0000000
--- a/media/java/android/media/session/RouteInfo.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 RouteInfo;
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/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 223013f..260ee397 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -74,7 +74,7 @@
</activity>
<receiver
- android:name=".NotificationController$NotificationBroadcastReceiver"
+ android:name=".model.NotificationController$NotificationBroadcastReceiver"
android:exported="false" >
</receiver>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index c17c73b..faad527 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -42,4 +42,7 @@
<integer name="print_option_column_count">2</integer>
+ <fraction name="page_selected_alpha">100%</fraction>
+ <fraction name="page_unselected_alpha">50%</fraction>
+
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index d37ccc0..3134e93 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -56,8 +56,6 @@
private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
- private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
- private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
private final Context mContext;
private final NotificationManager mNotificationManager;
@@ -69,7 +67,7 @@
}
public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
- List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
+ List<PrintJobInfo> notifyPrintJobs = new ArrayList<>();
final int printJobCount = printJobs.size();
for (int i = 0; i < printJobCount; i++) {
@@ -252,8 +250,6 @@
Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
- intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
- intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
}
@@ -302,17 +298,14 @@
String action = intent.getAction();
if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
- String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
- String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
- handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
+ handleCancelPrintJob(context, printJobId);
} else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
handleRestartPrintJob(context, printJobId);
}
}
- private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
- final String printJobLabel, final String printerName) {
+ private void handleCancelPrintJob(final Context context, final PrintJobId printJobId) {
if (DEBUG) {
Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index d802cd8..8a65a2e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -27,6 +27,7 @@
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -68,10 +69,10 @@
private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
private final List<PrinterInfo> mPrinters =
- new ArrayList<PrinterInfo>();
+ new ArrayList<>();
private final List<PrinterInfo> mFavoritePrinters =
- new ArrayList<PrinterInfo>();
+ new ArrayList<>();
private final PersistenceManager mPersistenceManager;
@@ -92,7 +93,7 @@
private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters,
ArrayMap<PrinterId, PrinterInfo> favoritePrinters) {
- List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ List<PrinterInfo> printers = new ArrayList<>();
// Add the updated favorite printers.
final int favoritePrinterCount = favoritePrinters.size();
@@ -142,7 +143,7 @@
// The contract is that if we already have a valid,
// result the we have to deliver it immediately.
if (!mPrinters.isEmpty()) {
- deliverResult(new ArrayList<PrinterInfo>(mPrinters));
+ deliverResult(new ArrayList<>(mPrinters));
}
// Always load the data to ensure discovery period is
// started and to make sure obsolete printers are updated.
@@ -184,11 +185,12 @@
+ mDiscoverySession.getPrinters().size()
+ " " + FusedPrintersProvider.this.hashCode());
}
+
updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
}
});
final int favoriteCount = mFavoritePrinters.size();
- List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+ List<PrinterId> printerIds = new ArrayList<>(favoriteCount);
for (int i = 0; i < favoriteCount; i++) {
printerIds.add(mFavoritePrinters.get(i).getId());
}
@@ -208,16 +210,19 @@
mPrintersUpdatedBefore = true;
- ArrayMap<PrinterId, PrinterInfo> printersMap =
- new ArrayMap<PrinterId, PrinterInfo>();
+ // Some of the found printers may have be a printer that is in the
+ // history but with its name changed. Hence, we try to update the
+ // printer to use its current name instead of the historical one.
+ mPersistenceManager.updatePrintersHistoricalNamesIfNeeded(printers);
+
+ ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<>();
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
printersMap.put(printer.getId(), printer);
}
- ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap =
- new ArrayMap<PrinterId, PrinterInfo>();
+ ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = new ArrayMap<>();
final int favoritePrinterCount = favoritePrinters.size();
for (int i = 0; i < favoritePrinterCount; i++) {
PrinterInfo favoritePrinter = favoritePrinters.get(i);
@@ -271,6 +276,10 @@
onStopLoading();
}
+ public boolean areHistoricalPrintersLoaded() {
+ return mPersistenceManager.mReadHistoryCompleted;
+ }
+
public void setTrackedPrinter(PrinterId printerId) {
if (isStarted() && mDiscoverySession != null
&& mDiscoverySession.isPrinterDiscoveryStarted()) {
@@ -306,7 +315,7 @@
for (int i = 0; i < favoritePrinterCount; i++) {
PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
if (favoritePrinter.getId().equals(printerId)) {
- newFavoritePrinters = new ArrayList<PrinterInfo>();
+ newFavoritePrinters = new ArrayList<>();
newFavoritePrinters.addAll(mPrinters);
newFavoritePrinters.remove(i);
break;
@@ -340,7 +349,7 @@
private final AtomicFile mStatePersistFile;
- private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>();
+ private List<PrinterInfo> mHistoricalPrinters = new ArrayList<>();
private boolean mReadHistoryCompleted;
private boolean mReadHistoryInProgress;
@@ -378,17 +387,42 @@
mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
- @SuppressWarnings("unchecked")
+ public void updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers) {
+ boolean writeHistory = false;
+
+ final int printerCount = printers.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = printers.get(i);
+ writeHistory |= renamePrinterIfNeeded(printer);
+ }
+
+ if (writeHistory) {
+ writePrinterHistory();
+ }
+ }
+
+ public boolean renamePrinterIfNeeded(PrinterInfo printer) {
+ boolean renamed = false;
+ final int printerCount = mHistoricalPrinters.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+ if (historicalPrinter.getId().equals(printer.getId())
+ && !TextUtils.equals(historicalPrinter.getName(), printer.getName())) {
+ mHistoricalPrinters.set(i, printer);
+ renamed = true;
+ }
+ }
+ return renamed;
+ }
+
public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
mHistoricalPrinters.remove(0);
}
mHistoricalPrinters.add(printer);
- new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
- new ArrayList<PrinterInfo>(mHistoricalPrinters));
+ writePrinterHistory();
}
- @SuppressWarnings("unchecked")
public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) {
boolean writeHistory = false;
final int printerCount = mHistoricalPrinters.size();
@@ -400,18 +434,22 @@
}
}
if (writeHistory) {
- new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
- new ArrayList<PrinterInfo>(mHistoricalPrinters));
+ writePrinterHistory();
}
}
+ @SuppressWarnings("unchecked")
+ private void writePrinterHistory() {
+ new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+ new ArrayList<>(mHistoricalPrinters));
+ }
+
public boolean isHistoryChanged() {
return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
}
private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
- Map<PrinterId, PrinterRecord> recordMap =
- new ArrayMap<PrinterId, PrinterRecord>();
+ Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<>();
// Recompute the weights.
float currentWeight = 1.0f;
@@ -429,14 +467,14 @@
}
// Soft the favorite printers.
- List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+ List<PrinterRecord> favoriteRecords = new ArrayList<>(
recordMap.values());
Collections.sort(favoriteRecords);
// Write the favorites to the output.
final int favoriteCount = Math.min(favoriteRecords.size(),
MAX_FAVORITE_PRINTER_COUNT);
- List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+ List<PrinterInfo> favoritePrinters = new ArrayList<>(favoriteCount);
for (int i = 0; i < favoriteCount; i++) {
PrinterInfo printer = favoriteRecords.get(i).printer;
favoritePrinters.add(printer);
@@ -478,7 +516,7 @@
List<PrintServiceInfo> services = printManager
.getEnabledPrintServices();
- Set<ComponentName> enabledComponents = new ArraySet<ComponentName>();
+ Set<ComponentName> enabledComponents = new ArraySet<>();
final int installedServiceCount = services.size();
for (int i = 0; i < installedServiceCount; i++) {
ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo;
@@ -524,28 +562,23 @@
Log.i(LOG_TAG, "No existing printer history "
+ FusedPrintersProvider.this.hashCode());
}
- return new ArrayList<PrinterInfo>();
+ return new ArrayList<>();
}
try {
- List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ List<PrinterInfo> printers = new ArrayList<>();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, null);
parseState(parser, printers);
// Take a note which version of the history was read.
mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified();
return printers;
- } catch (IllegalStateException ise) {
- Slog.w(LOG_TAG, "Failed parsing ", ise);
- } catch (NullPointerException npe) {
- Slog.w(LOG_TAG, "Failed parsing ", npe);
- } catch (NumberFormatException nfe) {
- Slog.w(LOG_TAG, "Failed parsing ", nfe);
- } catch (XmlPullParserException xppe) {
- Slog.w(LOG_TAG, "Failed parsing ", xppe);
- } catch (IOException ioe) {
- Slog.w(LOG_TAG, "Failed parsing ", ioe);
- } catch (IndexOutOfBoundsException iobe) {
- Slog.w(LOG_TAG, "Failed parsing ", iobe);
+ } catch (IllegalStateException
+ | NullPointerException
+ | NumberFormatException
+ | XmlPullParserException
+ | IOException
+ | IndexOutOfBoundsException e) {
+ Slog.w(LOG_TAG, "Failed parsing ", e);
} finally {
IoUtils.closeQuietly(in);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 8885a7b..eaf268d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -90,7 +90,10 @@
private int mSelectedPageCount;
private float mSelectedPageElevation;
+ private float mSelectedPageAlpha;
+
private float mUnselectedPageElevation;
+ private float mUnselectedPageAlpha;
private int mPreviewPageMargin;
private int mPreviewListPadding;
@@ -127,8 +130,13 @@
mSelectedPageElevation = mContext.getResources().getDimension(
R.dimen.selected_page_elevation);
+ mSelectedPageAlpha = mContext.getResources().getFraction(
+ R.fraction.page_selected_alpha, 1, 1);
+
mUnselectedPageElevation = mContext.getResources().getDimension(
R.dimen.unselected_page_elevation);
+ mUnselectedPageAlpha = mContext.getResources().getFraction(
+ R.fraction.page_unselected_alpha, 1, 1);
mPreviewPageMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.preview_page_margin);
@@ -339,9 +347,11 @@
if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) >= 0) {
checkbox.setChecked(true);
page.setTranslationZ(mSelectedPageElevation);
+ page.setAlpha(mSelectedPageAlpha);
} else {
checkbox.setChecked(false);
page.setTranslationZ(mUnselectedPageElevation);
+ page.setAlpha(mUnselectedPageAlpha);
}
TextView pageNumberView = (TextView) page.findViewById(R.id.page_number);
@@ -760,11 +770,13 @@
if (mConfirmedPagesInDocument.indexOfKey(pageInDocument) < 0) {
mConfirmedPagesInDocument.put(pageInDocument, null);
pageSelector.setChecked(true);
- page.animate().translationZ(mSelectedPageElevation);
+ page.animate().translationZ(mSelectedPageElevation)
+ .alpha(mSelectedPageAlpha);
} else {
mConfirmedPagesInDocument.remove(pageInDocument);
pageSelector.setChecked(false);
- page.animate().translationZ(mUnselectedPageElevation);
+ page.animate().translationZ(mUnselectedPageElevation)
+ .alpha(mUnselectedPageAlpha);
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 5ec2111..5e646ff 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -190,7 +190,7 @@
private MediaSizeComparator mMediaSizeComparator;
- private PrinterInfo mOldCurrentPrinter;
+ private PrinterInfo mCurrentPrinter;
private PageRange[] mSelectedPages;
@@ -576,7 +576,7 @@
}
}
- PrinterId printerId = mOldCurrentPrinter.getId();
+ PrinterId printerId = mCurrentPrinter.getId();
final int index = mDestinationSpinnerAdapter.getPrinterIndex(printerId);
mDestinationSpinner.setSelection(index);
}
@@ -810,7 +810,7 @@
}
private void requestCreatePdfFileOrFinish() {
- if (getCurrentPrinter() == mDestinationSpinnerAdapter.getPdfPrinter()) {
+ if (mCurrentPrinter == mDestinationSpinnerAdapter.getPdfPrinter()) {
startCreateDocumentActivity();
} else {
finish();
@@ -898,19 +898,14 @@
}
private void addCurrentPrinterToHistory() {
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter != null) {
+ if (mCurrentPrinter != null) {
PrinterId fakePdfPrinterId = mDestinationSpinnerAdapter.getPdfPrinter().getId();
- if (!currentPrinter.getId().equals(fakePdfPrinterId)) {
- mPrinterRegistry.addHistoricalPrinter(currentPrinter);
+ if (!mCurrentPrinter.getId().equals(fakePdfPrinterId)) {
+ mPrinterRegistry.addHistoricalPrinter(mCurrentPrinter);
}
}
}
- private PrinterInfo getCurrentPrinter() {
- return ((PrinterHolder) mDestinationSpinner.getSelectedItem()).printer;
- }
-
private void cancelPrint() {
setState(STATE_PRINT_CANCELED);
updateOptionsUi();
@@ -970,7 +965,6 @@
mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
mDestinationSpinner.setOnItemSelectedListener(itemSelectedListener);
- mDestinationSpinner.setSelection(0);
// Media size.
mMediaSizeSpinnerAdapter = new ArrayAdapter<>(
@@ -1036,16 +1030,14 @@
@Override
public void onClick(View view) {
if (view == mPrintButton) {
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter != null) {
+ if (mCurrentPrinter != null) {
confirmPrint();
} else {
cancelPrint();
}
} else if (view == mMoreOptionsButton) {
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter != null) {
- startAdvancedPrintOptionsActivity(currentPrinter);
+ if (mCurrentPrinter != null) {
+ startAdvancedPrintOptionsActivity(mCurrentPrinter);
}
}
}
@@ -1090,8 +1082,7 @@
// If no current printer, or it has no capabilities, or it is not
// available, we disable all print options except the destination.
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter == null || !canPrint(currentPrinter)) {
+ if (mCurrentPrinter == null || !canPrint(mCurrentPrinter)) {
mCopiesEditText.setEnabled(false);
mMediaSizeSpinner.setEnabled(false);
mColorModeSpinner.setEnabled(false);
@@ -1103,7 +1094,7 @@
return;
}
- PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+ PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
PrintAttributes defaultAttributes = capabilities.getDefaults();
// Destination.
@@ -1291,7 +1282,7 @@
}
// Advanced print options
- ComponentName serviceName = currentPrinter.getId().getServiceName();
+ ComponentName serviceName = mCurrentPrinter.getId().getServiceName();
if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName(
this, serviceName))) {
mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE);
@@ -1302,7 +1293,7 @@
}
// Print
- if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+ if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
mPrintButton.setImageResource(com.android.internal.R.drawable.ic_print);
} else {
mPrintButton.setImageResource(com.android.internal.R.drawable.ic_menu_save);
@@ -1317,7 +1308,7 @@
}
// Copies
- if (mDestinationSpinnerAdapter.getPdfPrinter() != currentPrinter) {
+ if (mDestinationSpinnerAdapter.getPdfPrinter() != mCurrentPrinter) {
mCopiesEditText.setEnabled(true);
} else {
mCopiesEditText.setEnabled(false);
@@ -1395,8 +1386,7 @@
}
public void onPrinterAvailable(PrinterInfo printer) {
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter.equals(printer)) {
+ if (mCurrentPrinter.equals(printer)) {
setState(STATE_CONFIGURING);
if (canUpdateDocument()) {
updateDocument(true, false);
@@ -1407,7 +1397,7 @@
}
public void onPrinterUnavailable(PrinterInfo printer) {
- if (getCurrentPrinter().getId().equals(printer.getId())) {
+ if (mCurrentPrinter.getId().equals(printer.getId())) {
setState(STATE_PRINTER_UNAVAILABLE);
if (mPrintedDocument.isUpdating()) {
mPrintedDocument.cancel();
@@ -1444,15 +1434,14 @@
return false;
}
- PrinterInfo currentPrinter = getCurrentPrinter();
- if (currentPrinter == null) {
+ if (mCurrentPrinter == null) {
return false;
}
- PrinterCapabilitiesInfo capabilities = currentPrinter.getCapabilities();
+ PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
if (capabilities == null) {
return false;
}
- if (currentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+ if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
return false;
}
@@ -1560,8 +1549,13 @@
private final PrinterHolder mFakePdfPrinterHolder;
+ private boolean mHistoricalPrintersLoaded;
+
public DestinationAdapter() {
- addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+ mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+ if (mHistoricalPrintersLoaded) {
+ addPrinters(mPrinterHolders, mPrinterRegistry.getPrinters());
+ }
mPrinterRegistry.setOnPrintersChangeListener(this);
mFakePdfPrinterHolder = new PrinterHolder(createFakePdfPrinter());
}
@@ -1602,7 +1596,10 @@
@Override
public int getCount() {
- return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+ if (mHistoricalPrintersLoaded) {
+ return Math.min(mPrinterHolders.size() + 2, DEST_ADAPTER_MAX_ITEM_COUNT);
+ }
+ return 0;
}
@Override
@@ -1731,6 +1728,12 @@
// not shown in the initial short list. Therefore, we have
// to keep the printer order.
+ // Check if historical printers are loaded as this adapter is open
+ // for busyness only if they are. This member is updated here and
+ // when the adapter is created because the historical printers may
+ // be loaded before or after the adapter is created.
+ mHistoricalPrintersLoaded = mPrinterRegistry.areHistoricalPrintersLoaded();
+
// No old printers - do not bother keeping their position.
if (mPrinterHolders.isEmpty()) {
addPrinters(mPrinterHolders, printers);
@@ -1839,7 +1842,7 @@
private final class PrintersObserver extends DataSetObserver {
@Override
public void onChanged() {
- PrinterInfo oldPrinterState = mOldCurrentPrinter;
+ PrinterInfo oldPrinterState = mCurrentPrinter;
if (oldPrinterState == null) {
return;
}
@@ -1927,14 +1930,15 @@
return;
}
- PrinterInfo currentPrinter = getCurrentPrinter();
+ PrinterHolder currentItem = (PrinterHolder) mDestinationSpinner.getSelectedItem();
+ PrinterInfo currentPrinter = (currentItem != null) ? currentItem.printer : null;
// Why on earth item selected is called if no selection changed.
- if (mOldCurrentPrinter == currentPrinter) {
+ if (mCurrentPrinter == currentPrinter) {
return;
}
- mOldCurrentPrinter = currentPrinter;
+ mCurrentPrinter = currentPrinter;
PrinterHolder printerHolder = mDestinationSpinnerAdapter.getPrinterHolder(
currentPrinter.getId());
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
index 7816d66..a3d7f01 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java
@@ -83,6 +83,10 @@
getPrinterProvider().setTrackedPrinter(printerId);
}
+ public boolean areHistoricalPrintersLoaded() {
+ return getPrinterProvider().areHistoricalPrintersLoaded();
+ }
+
private FusedPrintersProvider getPrinterProvider() {
Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER);
return (FusedPrintersProvider) loader;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index afdbb2a..555aa97 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -147,6 +147,7 @@
mExpandCollapseIcon = findViewById(R.id.expand_collapse_icon);
mExpandCollapseHandle.setOnClickListener(this);
+ mSummaryContent.setOnClickListener(this);
// Make sure we start in a closed options state.
onDragProgress(1.0f);
@@ -154,7 +155,7 @@
@Override
public void onClick(View view) {
- if (view == mExpandCollapseHandle) {
+ if (view == mExpandCollapseHandle || view == mSummaryContent) {
if (isOptionsClosed() && mOptionsStateController.canOpenOptions()) {
openOptions();
} else if (isOptionsOpened() && mOptionsStateController.canCloseOptions()) {
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
deleted file mode 100644
index a6c2cf8..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_01.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
deleted file mode 100644
index 4107c46..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_02.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M10.24,2.43C9.67,1.88 8.83,1.88 8.28,2.45L2.34,8.48c-0.56,0.57 -0.55,1.41 0.02,1.96l11.3,11.13c0.57,0.56 1.41,0.55 1.96,-0.02l5.93,-6.03c0.56,-0.57 0.55,-1.41 -0.02,-1.96L10.24,2.43zM14.68,20.62L3.38,9.5l5.93,-6.03l11.3,11.13L14.68,20.62z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M16.91,2.71c3.23,1.64 5.39,4.94 5.62,8.76l1.5,0.07c-0.33,-6.22 -5.21,-11.24 -11.5,-11.52c-0.2,-0.01 -0.4,-0.02 -0.7,-0.03l3.63,3.96L16.91,2.71z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M7.09,21.29c-3.23,-1.64 -5.39,-4.94 -5.62,-8.76l-1.4,-0.06c0.23,6.22 5.11,11.24 11.4,11.51c0.2,0.01 0.4,0.02 0.7,0.03l-3.63,-3.96L7.09,21.29z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
deleted file mode 100644
index 127296c4..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_03.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M11.07,4.12c-0.43,-0.49 -1.11,-0.53 -1.6,-0.1L4.3,8.57C3.81,9.0 3.77,9.68 4.19,10.17l8.54,9.71c0.43,0.49 1.11,0.53 1.6,0.1l5.18,-4.55c0.49,-0.43 0.53,-1.11 0.1,-1.6L11.07,4.12zM13.61,19.17L5.08,9.46l5.18,-4.55l8.54,9.71L13.61,19.17z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M17.72,3.2c3.06,1.94 4.9,5.43 4.77,9.25l1.49,0.21C24.23,6.43 19.84,0.97 13.6,0.11c-0.2,-0.03 -0.4,-0.06 -0.69,-0.1l3.24,4.29L17.72,3.2z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M6.19,20.78c-3.06,-1.94 -4.9,-5.43 -4.77,-9.25l-1.39,-0.19c-0.36,6.21 4.03,11.67 10.27,12.53c0.2,0.03 0.4,0.06 0.69,0.1l-3.24,-4.29L6.19,20.78z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
deleted file mode 100644
index d00262ab..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_04.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M11.88,5.72c-0.3,-0.42 -0.83,-0.51 -1.25,-0.21L6.19,8.69c-0.42,0.3 -0.51,0.83 -0.21,1.25l5.95,8.34c0.3,0.42 0.83,0.51 1.25,0.21l4.45,-3.17c0.42,-0.3 0.51,-0.83 0.21,-1.25L11.88,5.72zM12.68,17.79L6.73,9.45l4.45,-3.17l5.95,8.34L12.68,17.79z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M18.8,4.01c2.79,2.32 4.16,6.01 3.54,9.78l1.45,0.4c1.06,-6.14 -2.59,-12.11 -8.67,-13.78c-0.19,-0.05 -0.39,-0.11 -0.68,-0.18l2.66,4.67L18.8,4.01z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M5.11,19.96c-2.79,-2.32 -4.16,-6.01 -3.54,-9.78L0.21,9.81c-1.15,6.11 2.5,12.09 8.57,13.75c0.19,0.05 0.39,0.11 0.68,0.18L6.8,19.08L5.11,19.96z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
deleted file mode 100644
index 570f51f..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_05.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M15.41,8.14c0.05,-0.42 -0.23,-0.78 -0.65,-0.83l-4.5,-0.54C9.84,6.72 9.48,7.0 9.43,7.42l-1.01,8.44c-0.05,0.42 0.23,0.78 0.65,0.83l4.5,0.54C14.0,17.28 14.35,17.0 14.4,16.58L15.41,8.14zM9.16,15.99l1.01,-8.44l4.5,0.54l-1.01,8.44L9.16,15.99z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M22.35,10.28c0.64,3.57 -0.69,7.28 -3.59,9.77l0.85,1.23c4.76,-4.01 5.82,-10.94 2.24,-16.12c-0.11,-0.16 -0.23,-0.33 -0.4,-0.58l-0.97,5.29L22.35,10.28z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M1.6,13.66c-0.64,-3.57 0.69,-7.28 3.59,-9.77L4.4,2.74c-4.82,3.93 -5.88,10.86 -2.3,16.04c0.11,0.16 0.23,0.33 0.4,0.58l0.97,-5.29L1.6,13.66z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
deleted file mode 100644
index aaf9356..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_06.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M15.62,13.08c0.29,-0.1 0.44,-0.39 0.34,-0.69L14.9,9.27c-0.1,-0.29 -0.39,-0.44 -0.69,-0.34l-5.86,1.98c-0.29,0.1 -0.44,0.39 -0.34,0.69l1.06,3.12c0.1,0.29 0.39,0.44 0.69,0.34L15.62,13.08zM8.51,11.44l5.86,-1.98l1.06,3.12l-5.86,1.98L8.51,11.44z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M18.53,20.21c-2.8,2.3 -6.69,2.95 -10.27,1.64L7.6,23.2c5.83,2.2 12.39,-0.26 15.16,-5.92c0.09,-0.18 0.18,-0.36 0.31,-0.63l-5.09,1.73L18.53,20.21z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M5.45,3.76c2.8,-2.3 6.69,-2.95 10.27,-1.64l0.62,-1.26C10.56,-1.42 4.0,1.04 1.22,6.69C1.13,6.87 1.05,7.05 0.91,7.32L6.0,5.59L5.45,3.76z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
deleted file mode 100644
index 330ce6a..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_07.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M12.23,14.48c0.13,0.16 0.35,0.17 0.5,0.04l1.66,-1.4c0.16,-0.13 0.17,-0.35 0.04,-0.5l-2.62,-3.11c-0.13,-0.16 -0.35,-0.17 -0.5,-0.04l-1.66,1.4c-0.16,0.13 -0.17,0.35 -0.04,0.5L12.23,14.48zM11.53,9.73l2.62,3.11l-1.66,1.4l-2.62,-3.11L11.53,9.73z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M6.04,20.63c-3.01,-2.02 -4.75,-5.56 -4.52,-9.37l-1.48,-0.25c-0.43,6.21 3.81,11.79 10.02,12.83c0.2,0.03 0.39,0.07 0.69,0.12l-3.12,-4.37L6.04,20.63z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M18.04,3.37c3.01,2.02 4.75,5.56 4.52,9.37l1.38,0.23c0.53,-6.2 -3.71,-11.77 -9.93,-12.81c-0.2,-0.03 -0.39,-0.07 -0.69,-0.12l3.12,4.37L18.04,3.37z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
deleted file mode 100644
index 1c7f1a1..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_08.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M10.55,12.73c-0.06,0.12 -0.02,0.25 0.1,0.32l1.26,0.68c0.12,0.06 0.25,0.02 0.32,-0.1l1.27,-2.36c0.06,-0.12 0.02,-0.25 -0.1,-0.32l-1.26,-0.68c-0.12,-0.06 -0.25,-0.02 -0.32,0.1L10.55,12.73zM13.29,11.15l-1.27,2.36l-1.26,-0.68l1.27,-2.36L13.29,11.15z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M1.55,12.95C1.18,9.34 2.78,5.74 5.86,3.47L5.1,2.18C0.05,5.83 -1.51,12.66 1.67,18.09c0.1,0.17 0.2,0.35 0.35,0.6l1.36,-5.2L1.55,12.95z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M22.5,11.12c0.37,3.61 -1.23,7.21 -4.31,9.47l0.71,1.21c5.1,-3.56 6.67,-10.39 3.48,-15.83c-0.1,-0.17 -0.2,-0.35 -0.35,-0.6l-1.36,5.2L22.5,11.12z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
deleted file mode 100644
index ebfbad6..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_09.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M11.45,12c0,0.14,0.12,0.25,0.25,0.25l0.06,0l0,-0.5l-0.06,0C11.57,11.75,11.45,11.86,11.45,12z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M11.77,11.62l-0.06,0.0c-0.21,0.0 -0.37,0.17 -0.38,0.37c0.0,0.21 0.17,0.37 0.37,0.37l0.06,0.0c0.0,0.07 0.06,0.12 0.12,0.13l0.62,0.0c0.07,0.0 0.12,-0.06 0.13,-0.12l0.0,-0.75c0.0,-0.07 -0.06,-0.12 -0.12,-0.13l-0.62,0.0C11.83,11.5 11.77,11.56 11.77,11.62zM12.33,12.0c0.0,0.07 -0.06,0.12 -0.13,0.12c-0.07,0.0 -0.12,-0.06 -0.12,-0.13c0.0,-0.07 0.06,-0.12 0.13,-0.12C12.28,11.88 12.33,11.93 12.33,12.0zM11.77,11.75l0.0,0.5l-0.06,0.0c-0.14,0.0 -0.26,-0.11 -0.25,-0.25c0.0,-0.14 0.12,-0.25 0.26,-0.25L11.77,11.75z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M3.83,5.44c2.21,-2.87 5.85,-4.38 9.64,-3.9l0.34,-1.46C7.64,-0.75 1.81,3.12 0.37,9.25C0.32,9.45 0.28,9.64 0.21,9.93L4.78,7.1L3.83,5.44zM20.28,18.53c-2.21,2.87 -5.85,4.38 -9.64,3.9l-0.32,1.36c6.15,0.93 11.99,-2.95 13.42,-9.08c0.05,-0.19 0.09,-0.39 0.16,-0.68l-4.57,2.83L20.28,18.53z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
deleted file mode 100644
index 21dda8c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_10.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M10.42,9.96c-0.42,0.42,-0.39,1.12,0.03,1.54l0.19,0.19l1.51,-1.53l-0.19,-0.19 C11.54,9.55,10.84,9.54,10.42,9.96z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M12.53,9.78l-0.19,-0.19c-0.63,-0.63 -1.66,-0.62 -2.28,0.02c-0.63,0.63 -0.62,1.66 0.02,2.28l0.19,0.19c-0.21,0.21 -0.21,0.55 0.01,0.76l1.92,1.89c0.21,0.21 0.55,0.21 0.76,-0.01l2.27,-2.3c0.21,-0.21 0.21,-0.55 -0.01,-0.76l-1.92,-1.89C13.08,9.56 12.74,9.56 12.53,9.78zM13.12,12.62c-0.21,0.21 -0.55,0.21 -0.76,0.01c-0.21,-0.21 -0.21,-0.55 -0.01,-0.76c0.21,-0.21 0.55,-0.21 0.76,-0.01C13.33,12.07 13.33,12.41 13.12,12.62zM12.15,10.16l-1.51,1.53l-0.19,-0.19c-0.42,-0.42 -0.45,-1.12 -0.03,-1.54c0.42,-0.42 1.12,-0.41 1.54,0.01L12.15,10.16z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M7.82,2.36c3.3,-1.49 7.23,-1.12 10.35,1.09l0.99,-1.13C14.09,-1.32 7.12,-0.64 2.97,4.1C2.84,4.25 2.71,4.4 2.51,4.62l5.36,-0.36L7.82,2.36zM16.18,21.64c-3.3,1.49 -7.23,1.12 -10.35,-1.09l-0.92,1.05c4.99,3.71 11.97,3.03 16.12,-1.71c0.13,-0.15 0.26,-0.3 0.46,-0.53l-5.36,0.36L16.18,21.64z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
deleted file mode 100644
index f4186fe..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_11.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M10.53,8.85c-0.7,0.37,-0.95,1.28,-0.58,1.98l0.17,0.32l2.55,-1.36L12.5,9.48 C12.12,8.78,11.23,8.48,10.53,8.85z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M13.3,9.46l-0.17,-0.32c-0.56,-1.05 -1.87,-1.45 -2.93,-0.89s-1.45,1.87 -0.89,2.93l0.17,0.32c-0.35,0.19 -0.48,0.62 -0.3,0.98l1.7,3.18c0.19,0.35 0.62,0.48 0.98,0.3l3.82,-2.04c0.35,-0.19 0.48,-0.62 0.3,-0.98l-1.7,-3.18C14.09,9.4 13.65,9.27 13.3,9.46zM12.92,13.34c-0.35,0.19 -0.79,0.06 -0.98,-0.3c-0.19,-0.35 -0.05,-0.79 0.3,-0.98c0.35,-0.19 0.79,-0.05 0.98,0.3C13.41,12.72 13.27,13.15 12.92,13.34zM12.67,9.8l-2.55,1.36l-0.17,-0.32c-0.37,-0.7 -0.13,-1.61 0.58,-1.98c0.7,-0.37 1.59,-0.08 1.97,0.63L12.67,9.8z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M11.48,1.5c3.62,-0.23 7.16,1.5 9.3,4.66l1.32,-0.71C18.65,0.27 11.89,-1.55 6.34,1.42C6.16,1.51 5.98,1.61 5.72,1.75l5.14,1.56L11.48,1.5zM12.52,22.5c-3.62,0.23 -7.16,-1.5 -9.3,-4.66L1.98,18.5c3.37,5.23 10.13,7.06 15.68,4.08c0.18,-0.09 0.35,-0.19 0.62,-0.33l-5.14,-1.56L12.52,22.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
deleted file mode 100644
index d408e28..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_12.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M10.83,8.49c-0.91,0.3,-1.39,1.31,-1.09,2.22l0.13,0.41l3.28,-1.08l-0.13,-0.41 C12.72,8.72,11.73,8.19,10.83,8.49z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M13.98,9.77l-0.13,-0.41C13.4,8.0 11.93,7.26 10.57,7.71c-1.36,0.45 -2.1,1.91 -1.65,3.27l0.13,0.41c-0.45,0.15 -0.7,0.64 -0.55,1.09l1.35,4.1c0.15,0.45 0.64,0.7 1.09,0.55l4.92,-1.62c0.45,-0.15 0.7,-0.64 0.55,-1.09l-1.35,-4.1C14.92,9.87 14.43,9.62 13.98,9.77zM12.73,14.27c-0.45,0.15 -0.94,-0.1 -1.09,-0.55c-0.15,-0.45 0.1,-0.94 0.55,-1.09c0.45,-0.15 0.94,0.1 1.09,0.55C13.43,13.64 13.18,14.12 12.73,14.27zM13.16,10.04l-3.28,1.08l-0.13,-0.41c-0.3,-0.91 0.18,-1.92 1.09,-2.22c0.91,-0.3 1.9,0.24 2.19,1.14L13.16,10.04z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M13.55,1.6c3.59,0.48 6.72,2.87 8.21,6.39l1.44,-0.44C20.82,1.8 14.54,-1.31 8.51,0.52c-0.19,0.06 -0.38,0.12 -0.67,0.2l4.74,2.53L13.55,1.6zM10.45,22.4c-3.59,-0.48 -6.72,-2.87 -8.21,-6.39L0.9,16.41c2.28,5.79 8.55,8.9 14.58,7.07c0.19,-0.06 0.38,-0.12 0.67,-0.2l-4.74,-2.53L10.45,22.4z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
deleted file mode 100644
index 1ac6b39..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_13.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M11.17,7.71c-1.09,0.19,-1.8,1.28,-1.61,2.37l0.09,0.49l3.94,-0.7l-0.09,-0.49C13.3,8.3,12.26,7.52,11.17,7.71 z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M14.57,9.7l-0.09,-0.49C14.19,7.58 12.63,6.49 11.0,6.78c-1.63,0.29 -2.71,1.85 -2.43,3.48l0.08,0.49c-0.54,0.1 -0.91,0.62 -0.81,1.16l0.87,4.92c0.1,0.54 0.62,0.91 1.16,0.81l5.91,-1.05c0.54,-0.1 0.91,-0.61 0.81,-1.16l-0.87,-4.92C15.63,9.97 15.11,9.61 14.57,9.7zM12.4,14.66c-0.54,0.1 -1.06,-0.27 -1.16,-0.81c-0.1,-0.54 0.27,-1.06 0.81,-1.16s1.06,0.27 1.16,0.81C13.3,14.04 12.94,14.56 12.4,14.66zM13.58,9.88l-3.94,0.7l-0.09,-0.49c-0.19,-1.09 0.52,-2.17 1.61,-2.37s2.13,0.58 2.33,1.67L13.58,9.88z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M14.99,1.92c3.49,0.97 6.26,3.78 7.24,7.48l1.48,-0.23c-1.55,-6.03 -7.32,-9.99 -13.55,-9.02c-0.2,0.03 -0.4,0.06 -0.69,0.11l4.34,3.17L14.99,1.92zM9.01,22.08C5.52,21.1 2.76,18.3 1.78,14.6L0.4,14.82c1.45,6.05 7.22,10.01 13.45,9.04c0.2,-0.03 0.4,-0.06 0.69,-0.11l-4.34,-3.17L9.01,22.08z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
deleted file mode 100644
index c43e363c..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_14.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M11.72,7.61c-1.1,0.08,-1.93,1.08,-1.86,2.18l0.03,0.5l3.99,-0.27l-0.03,-0.5 C13.78,8.42,12.82,7.54,11.72,7.61z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M14.88,9.95l-0.03,-0.5c-0.11,-1.65 -1.54,-2.9 -3.2,-2.79C10.0,6.78 8.75,8.21 8.87,9.86l0.03,0.5c-0.55,0.04 -0.97,0.52 -0.93,1.07l0.34,4.99c0.04,0.55 0.52,0.97 1.07,0.93l5.99,-0.41c0.55,-0.04 0.97,-0.51 0.93,-1.07l-0.34,-4.99C15.91,10.33 15.43,9.91 14.88,9.95zM12.2,14.64c-0.55,0.04 -1.03,-0.38 -1.07,-0.93c-0.04,-0.55 0.38,-1.03 0.93,-1.07c0.55,-0.04 1.03,0.38 1.07,0.93C13.16,14.13 12.75,14.61 12.2,14.64zM13.88,10.02l-3.99,0.27l-0.03,-0.5c-0.08,-1.1 0.75,-2.11 1.86,-2.18c1.1,-0.08 2.06,0.81 2.13,1.91L13.88,10.02z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M15.71,2.17c3.41,1.23 5.96,4.23 6.67,7.98l1.5,-0.12c-1.1,-6.13 -6.58,-10.5 -12.86,-9.99c-0.2,0.02 -0.4,0.03 -0.7,0.06l4.1,3.48L15.71,2.17zM8.29,21.83c-3.41,-1.23 -5.96,-4.23 -6.67,-7.98l-1.4,0.11c1.0,6.14 6.48,10.51 12.76,9.99c0.2,-0.02 0.4,-0.03 0.7,-0.06l-4.1,-3.48L8.29,21.83z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
deleted file mode 100644
index 22fa428..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_15.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:pathData="M12.05,7.7c-1.1,0,-2,0.94,-2,2.05v0.5h4v-0.5C14.05,8.65,13.15,7.7,12.05,7.7z"
- android:fill="#00000000"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M15.05,10.25l0.0,-0.5c0.0,-1.66 -1.34,-3.0 -3.0,-3.0s-2.99,1.34 -2.99,3.0l-0.01,0.5c-0.55,0.0 -1.0,0.45 -1.0,1.0l0.0,5.0c0.0,0.55 0.45,1.0 1.0,1.0l6.0,0.0c0.55,0.0 1.0,-0.45 1.0,-1.0l0.0,-5.0C16.05,10.7 15.6,10.25 15.05,10.25zM12.05,14.75c-0.55,0.0 -1.0,-0.45 -1.0,-1.0c0.0,-0.55 0.45,-1.0 1.0,-1.0s1.0,0.45 1.0,1.0C13.05,14.3 12.6,14.75 12.05,14.75zM14.05,10.25l-4.0,0.0l0.0,-0.5c0.0,-1.1 0.9,-2.05 2.0,-2.05s2.0,0.94 2.0,2.05L14.05,10.25z"/>
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M16.5,2.5c3.3,1.5 5.6,4.7 6.0,8.5L24.0,11.0C23.4,4.8 18.3,0.0 12.0,0.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.5,2.5zM7.5,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5L0.1,13.0C0.6,19.2 5.7,24.0 12.0,24.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.5,21.5z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
new file mode 100644
index 0000000..e4c7cb5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_landscape.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64dp"
+ android:height="64dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M2.0,14.0l0.0,20.0c0.0,2.2 1.8,4.0 4.0,4.0l36.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,14.0c0.0,-2.2 -1.8,-4.0 -4.0,-4.0L6.0,10.0C3.8,10.0 2.0,11.8 2.0,14.0zM38.0,14.0l0.0,20.0L10.0,34.0L10.0,14.0L38.0,14.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
deleted file mode 100644
index 75e20f0..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_locked.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-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.
--->
-<animation-list
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="true">
- <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
-</animation-list>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
new file mode 100644
index 0000000..e4bf367
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_portrait.xml
@@ -0,0 +1,28 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64dp"
+ android:height="64dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M34.0,2.0L14.0,2.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,36.0c0.0,2.2 1.8,4.0 4.0,4.0l20.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L38.0,6.0C38.0,3.8 36.2,2.0 34.0,2.0zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0L34.0,38.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
index a1cedb9..a6c2cf8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_rotation_unlocked.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 The Android Open Source Project
@@ -14,22 +13,22 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<animation-list
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="true">
- <item android:drawable="@drawable/ic_qs_rotation_15" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_14" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_13" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_12" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_11" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_10" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_09" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_08" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_07" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_06" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_05" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_04" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_03" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_02" android:duration="16" />
- <item android:drawable="@drawable/ic_qs_rotation_01" android:duration="16" />
-</animation-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64dp"
+ android:height="64dp"/>
+
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M10.25,1.75c-0.6,-0.6 -1.5,-0.6 -2.1,0.0l-6.4,6.4c-0.6,0.6 -0.6,1.5 0.0,2.1l12.0,12.0c0.6,0.6 1.5,0.6 2.1,0.0l6.4,-6.4c0.6,-0.6 0.6,-1.5 0.0,-2.1L10.25,1.75zM14.85,21.25l-12.0,-12.0l6.4,-6.4l12.0,12.0L14.85,21.25z"/>
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M16.55,2.5c3.3,1.5 5.6,4.7 6.0,8.5l1.5,0.0c-0.6,-6.2 -5.7,-11.0 -12.0,-11.0c-0.2,0.0 -0.4,0.0 -0.7,0.0l3.8,3.8L16.55,2.5z"/>
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M7.55,21.5c-3.3,-1.5 -5.6,-4.7 -6.0,-8.5l-1.4,0.0c0.5,6.2 5.6,11.0 11.9,11.0c0.2,0.0 0.4,0.0 0.7,0.0l-3.8,-3.8L7.55,21.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index bae7ed7..3d53f9c 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -44,6 +44,7 @@
<color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
<color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
<color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
+ <color name="data_usage_graph_warning">#FFFFFFFF</color>
<color name="status_bar_clock_color">#33FFFFFF</color>
<!-- Tint color for the content on the notification overflow card. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6a7b450..b79dbbe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -572,6 +572,8 @@
<string name="quick_settings_cellular_detail_data_used"><xliff:g id="data_used" example="2.0 GB">%s</xliff:g> used</string>
<!-- QuickSettings: Cellular detail panel, data limit format string [CHAR LIMIT=NONE] -->
<string name="quick_settings_cellular_detail_data_limit"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> limit</string>
+ <!-- QuickSettings: Cellular detail panel, data warning format string [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_cellular_detail_data_warning"><xliff:g id="data_limit" example="2.0 GB">%s</xliff:g> warning</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">No recent apps</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
index fa11af6..d55ceaa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/DataUsageGraph.java
@@ -28,34 +28,34 @@
public class DataUsageGraph extends View {
- private final int mBackgroundColor;
private final int mTrackColor;
private final int mUsageColor;
private final int mOverlimitColor;
+ private final int mWarningColor;
private final int mMarkerWidth;
private final RectF mTmpRect = new RectF();
private final Paint mTmpPaint = new Paint();
- private long mMaxLevel = 1;
private long mLimitLevel;
private long mWarningLevel;
private long mUsageLevel;
+ private long mMaxLevel;
public DataUsageGraph(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = context.getResources();
- mBackgroundColor = res.getColor(R.color.system_primary_color);
mTrackColor = res.getColor(R.color.data_usage_graph_track);
mUsageColor = res.getColor(R.color.system_accent_color);
mOverlimitColor = res.getColor(R.color.system_warning_color);
+ mWarningColor = res.getColor(R.color.data_usage_graph_warning);
mMarkerWidth = res.getDimensionPixelSize(R.dimen.data_usage_graph_marker_width);
}
- public void setLevels(long maxLevel, long limitLevel, long warningLevel, long usageLevel) {
- mMaxLevel = Math.max(maxLevel, 1);
- mLimitLevel = limitLevel;
- mWarningLevel = warningLevel;
- mUsageLevel = usageLevel;
+ public void setLevels(long limitLevel, long warningLevel, long usageLevel) {
+ mLimitLevel = Math.max(0, limitLevel);
+ mWarningLevel = Math.max(0, warningLevel);
+ mUsageLevel = Math.max(0, usageLevel);
+ mMaxLevel = Math.max(Math.max(Math.max(mLimitLevel, mWarningLevel), mUsageLevel), 1);
postInvalidate();
}
@@ -68,21 +68,22 @@
final int w = getWidth();
final int h = getHeight();
- // draw track
- r.set(0, 0, w, h);
- p.setColor(mTrackColor);
- canvas.drawRect(r, p);
-
- final boolean hasLimit = mLimitLevel > 0;
- final boolean overLimit = hasLimit && mUsageLevel > mLimitLevel;
-
- final long maxLevel = hasLimit ? Math.max(mUsageLevel, mLimitLevel) : mMaxLevel;
- final long usageLevel = hasLimit ? Math.min(mUsageLevel, mLimitLevel) : mUsageLevel;
- float usageRight = w * (usageLevel / (float) maxLevel);
+ final boolean overLimit = mLimitLevel > 0 && mUsageLevel > mLimitLevel;
+ float usageRight = w * (mUsageLevel / (float) mMaxLevel);
if (overLimit) {
- usageRight -= (mMarkerWidth / 2);
- usageRight = Math.min(usageRight, w - mMarkerWidth * 2);
- usageRight = Math.max(usageRight, mMarkerWidth);
+ // compute the gap
+ usageRight = w * (mLimitLevel / (float) mMaxLevel) - (mMarkerWidth / 2);
+ usageRight = Math.min(Math.max(usageRight, mMarkerWidth), w - mMarkerWidth * 2);
+
+ // draw overlimit
+ r.set(usageRight + mMarkerWidth, 0, w, h);
+ p.setColor(mOverlimitColor);
+ canvas.drawRect(r, p);
+ } else {
+ // draw track
+ r.set(0, 0, w, h);
+ p.setColor(mTrackColor);
+ canvas.drawRect(r, p);
}
// draw usage
@@ -90,16 +91,11 @@
p.setColor(mUsageColor);
canvas.drawRect(r, p);
- if (overLimit) {
- // draw gap
- r.set(usageRight, 0, usageRight + mMarkerWidth, h);
- p.setColor(mBackgroundColor);
- canvas.drawRect(r, p);
-
- // draw overlimit
- r.set(usageRight + mMarkerWidth, 0, w, h);
- p.setColor(mOverlimitColor);
- canvas.drawRect(r, p);
- }
+ // draw warning marker
+ float warningLeft = w * (mWarningLevel / (float) mMaxLevel) - mMarkerWidth / 2;
+ warningLeft = Math.min(Math.max(warningLeft, 0), w - mMarkerWidth);
+ r.set(warningLeft, 0, warningLeft + mMarkerWidth, h);
+ p.setColor(mWarningColor);
+ canvas.drawRect(r, p);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 449cc1d..72474b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -108,6 +108,9 @@
mColumns = columns;
postInvalidate();
}
+ if (mListening) {
+ refreshAllTiles();
+ }
}
public void setExpanded(boolean expanded) {
@@ -123,9 +126,9 @@
mListening = listening;
for (TileRecord r : mRecords) {
r.tile.setListening(mListening);
- if (mListening) {
- r.tile.refreshState();
- }
+ }
+ if (mListening) {
+ refreshAllTiles();
}
if (listening) {
mBrightnessController.registerCallbacks();
@@ -134,6 +137,12 @@
}
}
+ private void refreshAllTiles() {
+ for (TileRecord r : mRecords) {
+ r.tile.refreshState();
+ }
+ }
+
private void showDetail(boolean show, TileRecord r) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 1f12b2a..d76a2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -216,21 +216,27 @@
final DataUsageInfo info = mController.getDataUsageInfo();
if (info == null) return v;
final Resources res = mContext.getResources();
- int titleId;
- long bytes;
+ final int titleId;
+ final long bytes;
int usageColor = R.color.system_accent_color;
- String top = null, bottom = null;
- if (info.limitLevel <= 0) { // no limit
+ final String top;
+ String bottom = null;
+ if (info.usageLevel < info.warningLevel || info.limitLevel <= 0) {
+ // under warning, or no limit
titleId = R.string.quick_settings_cellular_detail_data_usage;
bytes = info.usageLevel;
- } else if (info.usageLevel <= info.limitLevel) { // under limit
+ top = res.getString(R.string.quick_settings_cellular_detail_data_warning,
+ formatBytes(info.warningLevel));
+ } else if (info.usageLevel <= info.limitLevel) {
+ // over warning, under limit
titleId = R.string.quick_settings_cellular_detail_remaining_data;
bytes = info.limitLevel - info.usageLevel;
top = res.getString(R.string.quick_settings_cellular_detail_data_used,
formatBytes(info.usageLevel));
bottom = res.getString(R.string.quick_settings_cellular_detail_data_limit,
formatBytes(info.limitLevel));
- } else { // over limit
+ } else {
+ // over limit
titleId = R.string.quick_settings_cellular_detail_over_limit;
bytes = info.usageLevel - info.limitLevel;
top = res.getString(R.string.quick_settings_cellular_detail_data_used,
@@ -246,7 +252,7 @@
usage.setText(formatBytes(bytes));
usage.setTextColor(res.getColor(usageColor));
final DataUsageGraph graph = (DataUsageGraph) v.findViewById(R.id.usage_graph);
- graph.setLevels(info.maxLevel, info.limitLevel, info.warningLevel, info.usageLevel);
+ graph.setLevels(info.limitLevel, info.warningLevel, info.usageLevel);
final TextView carrier = (TextView) v.findViewById(R.id.usage_carrier_text);
carrier.setText(info.carrier);
final TextView period = (TextView) v.findViewById(R.id.usage_period_text);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 3be97cc..21cf9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -18,7 +18,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.drawable.AnimationDrawable;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
@@ -61,35 +60,19 @@
final boolean rotationLocked = mController.isRotationLocked();
state.visible = mController.isRotationLockAffordanceVisible();
final Resources res = mContext.getResources();
- if (state.value != rotationLocked) {
- state.value = rotationLocked;
- final AnimationDrawable d = (AnimationDrawable) res.getDrawable(rotationLocked
- ? R.drawable.ic_qs_rotation_locked
- : R.drawable.ic_qs_rotation_unlocked);
- state.icon = d;
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- d.start();
- }
- });
- }
+ state.value = rotationLocked;
if (rotationLocked) {
- final int lockOrientation = mController.getRotationLockOrientation();
- final int label = lockOrientation == Configuration.ORIENTATION_PORTRAIT
- ? R.string.quick_settings_rotation_locked_portrait_label
- : lockOrientation == Configuration.ORIENTATION_LANDSCAPE
- ? R.string.quick_settings_rotation_locked_landscape_label
- : R.string.quick_settings_rotation_locked_label;
+ final boolean portrait = res.getConfiguration().orientation
+ != Configuration.ORIENTATION_LANDSCAPE;
+ final int label = portrait ? R.string.quick_settings_rotation_locked_portrait_label
+ : R.string.quick_settings_rotation_locked_landscape_label;
+ final int icon = portrait ? R.drawable.ic_qs_rotation_portrait
+ : R.drawable.ic_qs_rotation_landscape;
state.label = mContext.getString(label);
- if (state.icon == null) {
- state.icon = res.getDrawable(R.drawable.ic_qs_rotation_15);
- }
+ state.icon = mContext.getDrawable(icon);
} else {
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
- if (state.icon == null) {
- state.icon = res.getDrawable(R.drawable.ic_qs_rotation_01);
- }
+ state.icon = res.getDrawable(R.drawable.ic_qs_rotation_unlocked);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 8a80b76..e7ac2e1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -32,7 +32,7 @@
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
// Enables clipping of tasks against each other
- public static final boolean EnableTaskStackClipping = true;
+ public static final boolean EnableTaskStackClipping = false;
// Enables tapping on the TaskBar to launch the task
public static final boolean EnableTaskBarTouchEvents = true;
// Enables app-info pane on long-pressing the icon
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 56de0be..1e581c1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -458,8 +458,6 @@
filter.addAction(ACTION_TOGGLE_RECENTS_ACTIVITY);
filter.addAction(ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
-
- mVisible = true;
}
@Override
@@ -485,6 +483,8 @@
}
}, 1);
}
+
+ mVisible = true;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
index 31825af..4c0ff48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
@@ -72,7 +72,12 @@
/** Adds a runnable to the last-decrement runnables list. */
public void addLastDecrementRunnable(Runnable r) {
+ // To ensure that the last decrement always calls, we increment and decrement after setting
+ // the last decrement runnable
+ boolean ensureLastDecrement = (mCount == 0);
+ if (ensureLastDecrement) increment();
mLastDecRunnables.add(r);
+ if (ensureLastDecrement) decrement();
}
/** Decrements the ref count */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index bda195b..607e155 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -16,16 +16,10 @@
package com.android.systemui.recents.misc;
-import android.app.ActivityManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.ParcelFileDescriptor;
import com.android.systemui.recents.RecentsConfiguration;
-import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -73,22 +67,25 @@
}
}
- /** Calculates the luminance-preserved greyscale of a given color. */
- public static int colorToGreyscale(int color) {
- return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
- 0.0722f * Color.blue(color));
- }
+ /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
+ public static float computeContrastBetweenColors(int bg, int fg) {
+ float bgR = Color.red(bg) / 255f;
+ float bgG = Color.green(bg) / 255f;
+ float bgB = Color.blue(bg) / 255f;
+ bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
+ bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
+ bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
+ float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
+
+ float fgR = Color.red(fg) / 255f;
+ float fgG = Color.green(fg) / 255f;
+ float fgB = Color.blue(fg) / 255f;
+ fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
+ fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
+ fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
+ float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
- /** Returns the ideal color to draw on top of a specified background color. */
- public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
- int darkRes) {
- return (greyscale < 128) ? lightRes : darkRes;
- }
- /** Returns the ideal drawable to draw on top of a specified background color. */
- public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
- Drawable lightRes,
- Drawable darkRes) {
- return (greyscale < 128) ? lightRes : darkRes;
+ return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
}
/** Sets some private shadow properties. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
index 1344729..757c07f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
@@ -29,6 +29,6 @@
@Override
protected int computeSize(Bitmap b) {
// The cache size will be measured in kilobytes rather than number of items
- return b.getAllocationByteCount() / 1024;
+ return b.getAllocationByteCount();
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
index 61d19da..5b50358 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
@@ -31,6 +31,6 @@
// The cache size will be measured in kilobytes rather than number of items
// NOTE: this isn't actually correct, as the icon may be smaller
int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
- return maxBytes / 1024;
+ return maxBytes;
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
index 3ccca9a2..5f4fabe 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
@@ -73,11 +73,6 @@
return mCache.get(key);
}
- /** Gets the previous task key that matches the specified key. */
- final Task.TaskKey getKey(Task.TaskKey key) {
- return mKeys.get(key);
- }
-
/** Puts an entry in the cache for a specific key. */
final void put(Task.TaskKey key, V value) {
mCache.put(key, value);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 2d50659..2f1c1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -45,7 +45,7 @@
mSystemServicesProxy = new SystemServicesProxy(context);
mCb = cb;
try {
- register(context, Looper.getMainLooper(), false);
+ register(context, Looper.getMainLooper(), true);
} catch (IllegalStateException e) {
e.printStackTrace();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 854ea1c..cbb8892 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,38 +27,29 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
-import android.util.Pair;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/** A bitmap load queue */
class TaskResourceLoadQueue {
ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
- ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
- new ConcurrentHashMap<Task.TaskKey, Boolean>();
-
- static final Boolean sFalse = new Boolean(false);
/** Adds a new task to the load queue */
- void addTask(Task t, boolean forceLoad) {
+ void addTask(Task t) {
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]");
}
if (!mQueue.contains(t)) {
mQueue.add(t);
}
- if (forceLoad) {
- mForceLoadSet.put(t.key, new Boolean(true));
- }
synchronized(this) {
notifyAll();
}
@@ -68,19 +59,11 @@
* Retrieves the next task from the load queue, as well as whether we want that task to be
* force reloaded.
*/
- Pair<Task, Boolean> nextTask() {
+ Task nextTask() {
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]");
}
- Task task = mQueue.poll();
- Boolean forceLoadTask = null;
- if (task != null) {
- forceLoadTask = mForceLoadSet.remove(task.key);
- }
- if (forceLoadTask == null) {
- forceLoadTask = sFalse;
- }
- return new Pair<Task, Boolean>(task, forceLoadTask);
+ return mQueue.poll();
}
/** Removes a task from the load queue */
@@ -89,7 +72,6 @@
Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]");
}
mQueue.remove(t);
- mForceLoadSet.remove(t.key);
}
/** Clears all the tasks from the load queue */
@@ -98,7 +80,6 @@
Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]");
}
mQueue.clear();
- mForceLoadSet.clear();
}
/** Returns whether the load queue is empty */
@@ -119,19 +100,20 @@
DrawableLruCache mApplicationIconCache;
BitmapLruCache mThumbnailCache;
Bitmap mDefaultThumbnail;
+ BitmapDrawable mDefaultApplicationIcon;
boolean mCancelled;
boolean mWaitingOnLoadQueue;
/** Constructor, creates a new loading thread that loads task resources in the background */
- public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
- DrawableLruCache applicationIconCache,
- BitmapLruCache thumbnailCache,
- Bitmap defaultThumbnail) {
+ public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
+ BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
+ BitmapDrawable defaultApplicationIcon) {
mLoadQueue = loadQueue;
mApplicationIconCache = applicationIconCache;
mThumbnailCache = thumbnailCache;
mDefaultThumbnail = defaultThumbnail;
+ mDefaultApplicationIcon = defaultApplicationIcon;
mMainThreadHandler = new Handler();
mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
@@ -200,59 +182,51 @@
SystemServicesProxy ssp = mSystemServicesProxy;
// Load the next item from the queue
- Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
- final Task t = nextTaskData.first;
- final boolean forceLoadTask = nextTaskData.second;
+ final Task t = mLoadQueue.nextTask();
if (t != null) {
- Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
- Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
+ Drawable cachedIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
+ Bitmap cachedThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader,
" [TaskResourceLoader|load]",
- t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
- " forceLoad: " + forceLoadTask);
+ t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
}
- // Load the application icon
- if (loadIcon == null || forceLoadTask) {
+ // Load the application icon if it is stale or we haven't cached one yet
+ if (cachedIcon == null) {
+ Drawable icon = null;
ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
t.userId);
- Drawable icon = ssp.getActivityIcon(info, t.userId);
- if (!mCancelled) {
- if (icon != null) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [TaskResourceLoader|loadIcon]", icon);
- }
- loadIcon = icon;
- mApplicationIconCache.put(t.key, icon);
+ if (info != null) {
+ icon = ssp.getActivityIcon(info, t.userId);
+ if (Console.Enabled) {
+ Console.log(Constants.Log.App.TaskDataLoader,
+ " [TaskResourceLoader|loadedIcon]", icon);
}
}
+ // If we can't load the icon, then set the default application icon into the
+ // cache. This will remain until the task's last active time is updated.
+ cachedIcon = icon != null ? icon : mDefaultApplicationIcon;
+ mApplicationIconCache.put(t.key, cachedIcon);
}
- // Load the thumbnail
- if (loadThumbnail == null || forceLoadTask) {
+ // Load the thumbnail if it is stale or we haven't cached one yet
+ if (cachedThumbnail == null) {
Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
- if (!mCancelled) {
- if (thumbnail != null) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [TaskResourceLoader|loadThumbnail]", thumbnail);
- }
- thumbnail.setHasAlpha(false);
- loadThumbnail = thumbnail;
- } else {
- loadThumbnail = mDefaultThumbnail;
- Console.logError(mContext,
- "Failed to load task top thumbnail for: " +
- t.key.baseIntent.getComponent().getPackageName());
+ if (thumbnail != null) {
+ thumbnail.setHasAlpha(false);
+ if (Console.Enabled) {
+ Console.log(Constants.Log.App.TaskDataLoader,
+ " [TaskResourceLoader|loadedThumbnail]", thumbnail);
}
- // We put the default thumbnail in the cache anyways
- mThumbnailCache.put(t.key, loadThumbnail);
}
+ // Even if we can't load the icon, we set the default thumbnail into the
+ // cache. This will remain until the task's last active time is updated.
+ cachedThumbnail = thumbnail != null ? thumbnail : mDefaultThumbnail;
+ mThumbnailCache.put(t.key, cachedThumbnail);
}
if (!mCancelled) {
// Notify that the task data has changed
- final Drawable newIcon = loadIcon;
- final Bitmap newThumbnail = loadThumbnail;
+ final Drawable newIcon = cachedIcon;
+ final Bitmap newThumbnail = cachedThumbnail;
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
@@ -306,11 +280,11 @@
/** Private Constructor */
private RecentsTaskLoader(Context context) {
// Calculate the cache sizes, we just use a reasonable number here similar to those
- // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
+ // suggested in the Android docs, 1/6th for the thumbnail cache and 1/30 of the max memory
// for icons.
- int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
- mMaxThumbnailCacheSize = maxMemory / 8;
- mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
+ int maxMemory = (int) Runtime.getRuntime().maxMemory();
+ mMaxThumbnailCacheSize = maxMemory / 6;
+ mMaxIconCacheSize = mMaxThumbnailCacheSize / 5;
int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
mMaxIconCacheSize;
int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
@@ -340,7 +314,7 @@
mApplicationIconCache = new DrawableLruCache(iconCacheSize);
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
- mDefaultThumbnail);
+ mDefaultThumbnail, mDefaultApplicationIcon);
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader,
@@ -394,7 +368,7 @@
}
RecentsConfiguration config = RecentsConfiguration.getInstance();
Resources res = context.getResources();
- ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
+ LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
TaskStack stack = new TaskStack();
SpaceNode root = new SpaceNode();
root.setStack(stack);
@@ -446,15 +420,16 @@
if (isForemostTask) {
// We force loading the application icon for the foremost task
task.applicationIcon = ssp.getActivityIcon(info, task.userId);
- if (task.applicationIcon != null) {
- mApplicationIconCache.put(task.key, task.applicationIcon);
- } else {
+ if (task.applicationIcon == null) {
task.applicationIcon = mDefaultApplicationIcon;
}
+ // Even if we can't load the icon we set the default application icon into
+ // the cache. This will remain until the task's last active time is updated.
+ mApplicationIconCache.put(task.key, task.applicationIcon);
} else {
- // Either the task has updated, or we haven't cached any information for the
- // task, so reload it
- tasksToForceLoad.add(task);
+ // Either the task has changed since the last active time, or it was not
+ // previously cached, so try and load the task anew.
+ tasksToLoad.add(task);
}
}
@@ -473,11 +448,13 @@
} else {
task.thumbnail = mDefaultThumbnail;
}
+ // Even if we can't load the thumbnail we set the default thumbnail into
+ // the cache. This will remain until the task's last active time is updated.
mThumbnailCache.put(task.key, task.thumbnail);
} else {
- // Either the task has updated, or we haven't cached any information for the
- // task, so reload it
- tasksToForceLoad.add(task);
+ // Either the task has changed since the last active time, or it was not
+ // previously cached, so try and load the task anew.
+ tasksToLoad.add(task);
}
}
}
@@ -496,14 +473,14 @@
}
// Simulate the groupings that we describe
- stack.createSimulatedAffiliatedGroupings();
+ stack.createAffiliatedGroupings();
// Start the task loader
mLoader.start(context);
- // Add all the tasks that we are force/re-loading
- for (Task t : tasksToForceLoad) {
- mLoadQueue.addTask(t, true);
+ // Add all the tasks that we are reloading
+ for (Task t : tasksToLoad) {
+ mLoadQueue.addTask(t);
}
// Update the package monitor with the list of packages to listen for
@@ -526,7 +503,7 @@
stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
}
- stack.createSimulatedAffiliatedGroupings();
+ stack.createAffiliatedGroupings();
return stack;
}
@@ -551,7 +528,7 @@
requiresLoad = true;
}
if (requiresLoad) {
- mLoadQueue.addTask(t, false);
+ mLoadQueue.addTask(t);
}
t.notifyTaskDataLoaded(thumbnail, applicationIcon);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 88e9f40..1670735 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import com.android.systemui.recents.misc.Utilities;
@@ -84,7 +85,7 @@
public Drawable activityIcon;
public String activityLabel;
public int colorPrimary;
- public int colorPrimaryGreyscale;
+ public boolean useLightOnPrimaryColor;
public Bitmap thumbnail;
public boolean isActive;
public boolean canLockToTask;
@@ -104,7 +105,8 @@
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
this.colorPrimary = colorPrimary;
- this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
+ this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(colorPrimary,
+ Color.WHITE) > 3f;
this.isActive = isActive;
this.canLockToTask = canLockToTask;
this.userId = userId;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 7dd15a6..e3bcff0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -320,7 +320,7 @@
/**
* Temporary: This method will simulate affiliation groups by
*/
- public void createSimulatedAffiliatedGroupings() {
+ public void createAffiliatedGroupings() {
if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
// Sort all tasks by increasing firstActiveTime of the task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 99b012e..73bbf86 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -191,10 +191,6 @@
/** Requests all task stacks to start their enter-recents animation */
public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
- // Handle the case when there are no views by incrementing and decrementing after all
- // animations are started.
- ctx.postAnimationTrigger.increment();
-
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
@@ -203,18 +199,10 @@
stackView.startEnterRecentsAnimation(ctx);
}
}
-
- // Handle the case when there are no views by incrementing and decrementing after all
- // animations are started.
- ctx.postAnimationTrigger.decrement();
}
/** Requests all task stacks to start their exit-recents animation */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
- // Handle the case when there are no views by incrementing and decrementing after all
- // animations are started.
- ctx.postAnimationTrigger.increment();
-
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
@@ -224,10 +212,6 @@
}
}
- // Handle the case when there are no views by incrementing and decrementing after all
- // animations are started.
- ctx.postAnimationTrigger.decrement();
-
// Notify of the exit animation
mCb.onExitToHomeAnimationTriggered();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index db84962..deb9df3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -162,11 +162,10 @@
}
// Try and apply the system ui tint
setBackgroundColor(t.colorPrimary);
- mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColorGreyscale(
- t.colorPrimaryGreyscale, mConfig.taskBarViewLightTextColor,
- mConfig.taskBarViewDarkTextColor));
- mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColorGreyscale(
- t.colorPrimaryGreyscale, mLightDismissDrawable, mDarkDismissDrawable));
+ mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
+ mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
+ mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
+ mLightDismissDrawable : mDarkDismissDrawable);
}
/** Unbinds the bar view from the task */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 599c590..adc808a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -302,7 +302,6 @@
int[] visibleRange = mTmpVisibleRange;
updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
TaskViewTransform tmpTransform = new TaskViewTransform();
- TaskStack.GroupTaskIndex gti = new TaskStack.GroupTaskIndex();
// Return all the invisible children to the pool
HashMap<Task, TaskView> taskChildViewMap = getTaskChildViewMap();
@@ -355,6 +354,47 @@
}
}
+ /** Updates the clip for each of the task views. */
+ void clipTaskViews() {
+ // Update the clip on each task child
+ if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount - 1; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ TaskView nextTv = null;
+ TaskView tmpTv = null;
+ int clipBottom = 0;
+ if (tv.shouldClipViewInStack()) {
+ // Find the next view to clip against
+ int nextIndex = i;
+ while (nextIndex < getChildCount()) {
+ tmpTv = (TaskView) getChildAt(++nextIndex);
+ if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
+ nextTv = tmpTv;
+ break;
+ }
+ }
+
+ // Clip against the next view, this is just an approximation since we are
+ // stacked and we can make assumptions about the visibility of the this
+ // task relative to the ones in front of it.
+ if (nextTv != null) {
+ // XXX: Can hash the visible rects for this run
+ tv.getHitRect(mTmpRect);
+ nextTv.getHitRect(mTmpRect2);
+ clipBottom = (mTmpRect.bottom - mTmpRect2.top);
+ }
+ }
+ tv.setClipFromBottom(clipBottom);
+ }
+ }
+ if (getChildCount() > 0) {
+ // The front most task should never be clipped
+ TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
+ tv.setClipFromBottom(0);
+ }
+ }
+
/** Sets the current stack scroll */
public void setStackScroll(int value) {
mStackScroll = value;
@@ -641,50 +681,10 @@
Console.AnsiPurple);
}
synchronizeStackViewsWithModel();
+ clipTaskViews();
super.dispatchDraw(canvas);
}
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (Constants.DebugFlags.App.EnableTaskStackClipping) {
- TaskView tv = (TaskView) child;
- TaskView nextTv = null;
- TaskView tmpTv = null;
- if (tv.shouldClipViewInStack()) {
- int curIndex = indexOfChild(tv);
-
- // Find the next view to clip against
- while (nextTv == null && curIndex < getChildCount()) {
- tmpTv = (TaskView) getChildAt(++curIndex);
- if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
- nextTv = tmpTv;
- }
- }
-
- // Clip against the next view (if we aren't animating its alpha)
- if (nextTv != null) {
- Rect curRect = tv.getClippingRect(mTmpRect);
- Rect nextRect = nextTv.getClippingRect(mTmpRect2);
- // The hit rects are relative to the task view, which needs to be offset by
- // the system bar height
- curRect.offset(0, mConfig.systemInsets.top);
- nextRect.offset(0, mConfig.systemInsets.top);
- // Compute the clip region
- Region clipRegion = new Region();
- clipRegion.op(curRect, Region.Op.UNION);
- clipRegion.op(nextRect, Region.Op.DIFFERENCE);
- // Clip the canvas
- int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
- canvas.clipRegion(clipRegion);
- boolean invalidate = super.drawChild(canvas, child, drawingTime);
- canvas.restoreToCount(saveCount);
- return invalidate;
- }
- }
- }
- return super.drawChild(canvas, child, drawingTime);
- }
-
/** Computes the stack and task rects */
public void computeRects(int width, int height, int insetLeft, int insetBottom) {
// Compute the rects in the stack algorithm
@@ -1155,6 +1155,11 @@
mStack.removeTask(task);
}
+ @Override
+ public void onTaskViewClipStateChanged(TaskView tv) {
+ invalidate(mStackAlgorithm.mStackRect);
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 908e063..9c48896 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -135,9 +135,9 @@
// Set the y translation
if (boundedT < 0f) {
transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
- numPeekCards) * peekHeight - scaleYOffset - scaleBarYOffset);
+ numPeekCards) * peekHeight - scaleYOffset);
} else {
- transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset - scaleBarYOffset);
+ transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
}
// Set the z translation
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 5524e15..ab14863 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -46,8 +46,9 @@
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
public void onTaskViewAppInfoClicked(TaskView tv);
- public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
+ public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
+ public void onTaskViewClipStateChanged(TaskView tv);
}
RecentsConfiguration mConfig;
@@ -65,7 +66,7 @@
boolean mIsFocused;
boolean mIsStub;
boolean mClipViewInStack;
- Rect mTmpRect = new Rect();
+ int mClipFromBottom;
Paint mLayerPaint = new Paint();
TaskThumbnailView mThumbnailView;
@@ -118,7 +119,9 @@
setOutlineProvider(new ViewOutlineProvider() {
@Override
public boolean getOutline(View view, Outline outline) {
- int height = getHeight() - mMaxFooterHeight + mFooterHeight;
+ // The current height is measured with the footer, so account for the footer height
+ // and the current clip (in the stack)
+ int height = getMeasuredHeight() - mClipFromBottom - mMaxFooterHeight + mFooterHeight;
outline.setRoundRect(0, 0, getWidth(), height,
mConfig.taskViewRoundedCornerRadiusPx);
return true;
@@ -483,15 +486,6 @@
mBarView.setNoUserInteractionState();
}
- /** Returns the rect we want to clip (it may not be the full rect) */
- Rect getClippingRect(Rect outRect) {
- getHitRect(outRect);
- // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
- outRect.right = outRect.left + mThumbnailView.getRight();
- outRect.bottom = outRect.top + mThumbnailView.getBottom();
- return outRect;
- }
-
/** Enable the hw layers on this task view */
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
@@ -506,7 +500,7 @@
mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
}
- /** Sets the stubbed state of this task view. */
+ /** Sets the stubbed state of this task view.
void setStubState(boolean isStub) {
if (!mIsStub && isStub) {
// This is now a stub task view, so clip to the bar height, hide the thumbnail
@@ -519,7 +513,7 @@
mThumbnailView.setVisibility(View.VISIBLE);
}
mIsStub = isStub;
- }
+ } */
/**
* Returns whether this view should be clipped, or any views below should clip against this
@@ -533,19 +527,26 @@
void setClipViewInStack(boolean clip) {
if (clip != mClipViewInStack) {
mClipViewInStack = clip;
- if (getParent() instanceof View) {
- getHitRect(mTmpRect);
- ((View) getParent()).invalidate(mTmpRect);
- }
+ mCb.onTaskViewClipStateChanged(this);
+ }
+ }
+
+ void setClipFromBottom(int clipFromBottom) {
+ clipFromBottom = Math.max(0, Math.min(getMeasuredHeight(), clipFromBottom));
+ if (mClipFromBottom != clipFromBottom) {
+ mClipFromBottom = clipFromBottom;
+ invalidateOutline();
}
}
/** Sets the footer height. */
- public void setFooterHeight(int height) {
- mFooterHeight = height;
- invalidateOutline();
- invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
- getMeasuredHeight());
+ public void setFooterHeight(int footerHeight) {
+ if (footerHeight != mFooterHeight) {
+ mFooterHeight = footerHeight;
+ invalidateOutline();
+ invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+ getMeasuredHeight());
+ }
}
/** Gets the footer height. */
@@ -677,6 +678,7 @@
mTask = t;
mTask.setCallbacks(this);
if (getMeasuredWidth() == 0) {
+ // If we haven't yet measured, we should just set the footer height with any animation
animateFooterVisibility(t.canLockToTask, 0, 0);
} else {
animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
index f2dfe05..33d68bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataController.java
@@ -20,6 +20,8 @@
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -33,19 +35,23 @@
import android.os.ServiceManager;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.text.format.Time;
import android.util.Log;
import com.android.systemui.statusbar.policy.NetworkController.DataUsageInfo;
-import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.Locale;
public class MobileDataController {
private static final String TAG = "MobileDataController";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final SimpleDateFormat MMM_D = new SimpleDateFormat("MMM d");
+ private static final long DEFAULT_WARNING_LEVEL = 2L * 1024 * 1024 * 1024;
private static final int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES;
+ private static final StringBuilder PERIOD_BUILDER = new StringBuilder(50);
+ private static final java.util.Formatter PERIOD_FORMATTER = new java.util.Formatter(
+ PERIOD_BUILDER, Locale.getDefault());
private final Context mContext;
private final TelephonyManager mTelephonyManager;
@@ -87,6 +93,13 @@
return null;
}
+ private static Time addMonth(Time t, int months) {
+ final Time rt = new Time(t);
+ rt.set(t.monthDay, t.month + months, t.year);
+ rt.normalize(false);
+ return rt;
+ }
+
public DataUsageInfo getDataUsageInfo() {
final String subscriberId = getActiveSubscriberId(mContext);
if (subscriberId == null) {
@@ -101,9 +114,28 @@
try {
final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELDS);
final long now = System.currentTimeMillis();
- // period = last 4 wks for now
- final long start = now - DateUtils.WEEK_IN_MILLIS * 4;
- final long end = now;
+ final long start, end;
+ if (policy != null && policy.cycleDay > 0) {
+ // period = determined from cycleDay
+ if (DEBUG) Log.d(TAG, "Cycle day=" + policy.cycleDay + " tz="
+ + policy.cycleTimezone);
+ final Time nowTime = new Time(policy.cycleTimezone);
+ nowTime.setToNow();
+ final Time policyTime = new Time(nowTime);
+ policyTime.set(policy.cycleDay, policyTime.month, policyTime.year);
+ policyTime.normalize(false);
+ if (nowTime.after(policyTime)) {
+ start = policyTime.toMillis(false);
+ end = addMonth(policyTime, 1).toMillis(false);
+ } else {
+ start = addMonth(policyTime, -1).toMillis(false);
+ end = policyTime.toMillis(false);
+ }
+ } else {
+ // period = last 4 wks
+ end = now;
+ start = now - DateUtils.WEEK_IN_MILLIS * 4;
+ }
final long callStart = System.currentTimeMillis();
final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
final long callEnd = System.currentTimeMillis();
@@ -115,12 +147,13 @@
}
final long totalBytes = entry.rxBytes + entry.txBytes;
final DataUsageInfo usage = new DataUsageInfo();
- usage.maxLevel = (long) (totalBytes / .4);
usage.usageLevel = totalBytes;
- usage.period = MMM_D.format(new Date(start)) + " - " + MMM_D.format(new Date(end));
+ usage.period = formatDateRange(start, end);
if (policy != null) {
usage.limitLevel = policy.limitBytes > 0 ? policy.limitBytes : 0;
usage.warningLevel = policy.warningBytes > 0 ? policy.warningBytes : 0;
+ } else {
+ usage.warningLevel = DEFAULT_WARNING_LEVEL;
}
return usage;
} catch (RemoteException e) {
@@ -178,6 +211,15 @@
return actualSubscriberId;
}
+ private String formatDateRange(long start, long end) {
+ final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+ synchronized (PERIOD_BUILDER) {
+ PERIOD_BUILDER.setLength(0);
+ return DateUtils.formatDateRange(mContext, PERIOD_FORMATTER, start, end, flags, null)
+ .toString();
+ }
+ }
+
public interface Callback {
void onMobileDataEnabled(boolean enabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index d058bd0..6d8b400 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -61,7 +61,6 @@
public static class DataUsageInfo {
public String carrier;
public String period;
- public long maxLevel;
public long limitLevel;
public long warningLevel;
public long usageLevel;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index ad2cf75..0c5d2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -908,6 +908,9 @@
if (mDialog != null) {
mDialog.show();
+ if (mCallback != null) {
+ mCallback.onVisible(true);
+ }
}
}
@@ -1160,6 +1163,9 @@
mDialog.dismiss();
clearRemoteStreamController();
mActiveStreamType = -1;
+ if (mCallback != null) {
+ mCallback.onVisible(false);
+ }
}
}
synchronized (sConfirmSafeVolumeLock) {
@@ -1262,5 +1268,6 @@
public interface Callback {
void onZenSettings();
void onInteraction();
+ void onVisible(boolean visible);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index e4f5870..375f94c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -99,6 +99,13 @@
kvm.userActivity();
}
}
+
+ @Override
+ public void onVisible(boolean visible) {
+ if (mAudioManager != null && mVolumeController != null) {
+ mAudioManager.notifyVolumeControllerVisible(mVolumeController, visible);
+ }
+ }
});
mDialogPanel = mPanel;
}
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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
if (mDestroyed) {
return;
}
- if (isConnected()) {
- disconnectLocked(MediaSession.DISCONNECT_REASON_SESSION_DESTROYED);
- }
- mRoute = null;
- mRequest = null;
mDestroyed = true;
}
}
@@ -532,15 +408,6 @@
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 @@
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 @@
}
}
- 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 @@
}
}
- 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 @@
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 @@
}
@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 @@
}
@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 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 @@
}
@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 @@
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.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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
/**
* Keeps track of media sessions and their priority for notifications, media
- * button routing, etc.
+ * button dispatch, etc.
*/
public class MediaSessionStack {
/**
@@ -277,8 +277,8 @@
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/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 5233297..c0923ca 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -656,14 +656,13 @@
}
break;
case MSG_USER_SWITCHED: {
- mCurrentUser = msg.arg1;
-
UserManager userManager =
(UserManager) mContext.getSystemService(Context.USER_SERVICE);
if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
Slog.v(TAG, "Switched to user with DISALLOW_USB_FILE_TRANSFER restriction;"
+ " disabling USB.");
setUsbConfig("none");
+ mCurrentUser = msg.arg1;
break;
}
@@ -675,6 +674,7 @@
setUsbConfig("none");
setUsbConfig(mCurrentFunctions);
}
+ mCurrentUser = msg.arg1;
break;
}
}
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_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.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 @@
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 void showRoutePicker() {
- mController.showRoutePicker();
+ // TODO
}
private void unbindFromService() {
@@ -173,11 +172,6 @@
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 @@
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.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 @@
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 @@
| 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 @@
}
}
}
-
- @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 @@
}
}
- 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 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.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 @@
| PlaybackState.ACTION_PLAY);
mRenderer.registerListener(mRenderListener);
-
- if (DEBUG) {
- Log.d(TAG, "onCreate, routeId is " + mRouteId);
- }
}
@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());
- }
+ 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;
}
- ArrayList<RouteInfo> result = new ArrayList<RouteInfo>();
- if (bob.getOptionsSize() > 0) {
- RouteInfo info = bob.build();
- result.add(info);
- }
- if (DEBUG) {
- Log.d(TAG, "getRoutes returning " + result.toString());
- }
- return result;
+ return new OneSession(client);
}
- @Override
- public RouteConnection connect(RouteInfo route, RouteRequest request) {
- if (mConnection != null) {
- disconnect(mConnection);
- }
- RouteConnection connection = new RouteConnection(this, route);
- mControls = RoutePlaybackControlsHandler.addTo(connection);
- mControls.addListener(new PlayHandler(mRouteId), mHandler);
- if (DEBUG) {
- Log.d(TAG, "Connected to route");
- }
- return connection;
- }
+ private final class OneSession extends ClientSession {
+ private final ClientInfo mClient;
- private class PlayHandler extends RoutePlaybackControlsHandler.Listener {
- private final String mRouteId;
-
- public PlayHandler(String routeId) {
- mRouteId = routeId;
+ public OneSession(ClientInfo client) {
+ mClient = client;
}
@Override
- public void playNow(String content, ResultReceiver cb) {
+ 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;
+ }
+
+ @Override
+ public void onStopDiscovery() {
+ }
+
+ @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;
+ }
+
+ @Override
+ public void onDisconnect() {
+ mStub = null;
+ }
+
+ 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;
+ }
+ }
+
+ private final class OneStub extends MediaPlayerProtocol.Stub {
+ MediaInfo mMediaInfo;
+
+ public OneStub() {
+ super(mHandler);
+ }
+
+ @Override
+ 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 @@
@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 @@
break;
}
- mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+ sendStatusUpdate(mPlaybackState.getState());
}
@Override
@@ -203,5 +226,36 @@
@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);
+ }
+ }
}
}