summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk13
-rw-r--r--api/current.txt260
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/content/Context.java4
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java15
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java4
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java140
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java13
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl3
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java43
-rw-r--r--core/java/android/provider/MediaStore.java20
-rw-r--r--core/java/android/util/Log.java1
-rw-r--r--core/java/android/view/View.java10
-rw-r--r--core/java/android/view/ViewGroup.java18
-rw-r--r--core/java/android/view/ViewRootImpl.java10
-rw-r--r--core/java/android/view/WindowInsets.java5
-rw-r--r--core/java/android/webkit/WebView.java20
-rw-r--r--core/java/android/webkit/WebViewProvider.java2
-rw-r--r--core/java/android/widget/TextView.java26
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java13
-rw-r--r--core/jni/android/graphics/Paint.cpp44
-rw-r--r--core/jni/android_util_Binder.cpp19
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--core/res/res/drawable/btn_borderless_quantum.xml1
-rw-r--r--core/res/res/drawable/btn_check_quantum_anim.xml40
-rw-r--r--core/res/res/layout/alert_dialog_quantum.xml26
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/styles_quantum.xml1
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/values/themes_quantum.xml4
-rw-r--r--docs/html/design/media/dialogs_examples.pngbin39873 -> 44959 bytes
-rw-r--r--docs/html/design/media/navigation_drawer_titles_icons.pngbin15000 -> 20467 bytes
-rw-r--r--docs/html/design/media/selection_adjusting_actions.pngbin190458 -> 197541 bytes
-rw-r--r--docs/html/design/media/touch_feedback_communication.pngbin199575 -> 203895 bytes
-rw-r--r--docs/html/design/media/ui_overview_notifications.pngbin42875 -> 49688 bytes
-rw-r--r--docs/html/guide/components/intents-common.jd243
-rw-r--r--docs/html/wear/index.jd4
-rw-r--r--graphics/java/android/graphics/Paint.java17
-rw-r--r--graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java4
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java16
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp19
-rw-r--r--media/java/android/media/Ringtone.java6
-rw-r--r--media/java/android/media/routeprovider/IRouteConnection.aidl28
-rw-r--r--media/java/android/media/routeprovider/IRouteProvider.aidl36
-rw-r--r--media/java/android/media/routeprovider/IRouteProviderCallback.aidl32
-rw-r--r--media/java/android/media/routeprovider/RouteConnection.java164
-rw-r--r--media/java/android/media/routeprovider/RouteInterfaceHandler.java245
-rw-r--r--media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java221
-rw-r--r--media/java/android/media/routeprovider/RouteProviderService.java227
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.aidl18
-rw-r--r--media/java/android/media/routeprovider/RouteRequest.java96
-rw-r--r--media/java/android/media/session/ISession.aidl (renamed from media/java/android/media/session/IMediaSession.aidl)20
-rw-r--r--media/java/android/media/session/ISessionCallback.aidl (renamed from media/java/android/media/session/IMediaSessionCallback.aidl)12
-rw-r--r--media/java/android/media/session/ISessionController.aidl (renamed from media/java/android/media/session/IMediaController.aidl)9
-rw-r--r--media/java/android/media/session/ISessionControllerCallback.aidl (renamed from media/java/android/media/session/IMediaControllerCallback.aidl)5
-rw-r--r--media/java/android/media/session/ISessionManager.aidl (renamed from media/java/android/media/session/IMediaSessionManager.aidl)8
-rw-r--r--media/java/android/media/session/PlaybackState.java11
-rw-r--r--media/java/android/media/session/Route.java99
-rw-r--r--media/java/android/media/session/RouteCommand.aidl (renamed from media/java/android/media/session/MediaSessionToken.aidl)2
-rw-r--r--media/java/android/media/session/RouteCommand.java117
-rw-r--r--media/java/android/media/session/RouteEvent.aidl18
-rw-r--r--media/java/android/media/session/RouteEvent.java120
-rw-r--r--media/java/android/media/session/RouteInfo.aidl18
-rw-r--r--media/java/android/media/session/RouteInfo.java233
-rw-r--r--media/java/android/media/session/RouteInterface.java219
-rw-r--r--media/java/android/media/session/RouteOptions.aidl18
-rw-r--r--media/java/android/media/session/RouteOptions.java163
-rw-r--r--media/java/android/media/session/RoutePlaybackControls.java161
-rw-r--r--media/java/android/media/session/RouteTransportControls.java230
-rw-r--r--media/java/android/media/session/Session.java (renamed from media/java/android/media/session/MediaSession.java)265
-rw-r--r--media/java/android/media/session/SessionController.java (renamed from media/java/android/media/session/MediaController.java)64
-rw-r--r--media/java/android/media/session/SessionInfo.java82
-rw-r--r--media/java/android/media/session/SessionManager.java (renamed from media/java/android/media/session/MediaSessionManager.java)28
-rw-r--r--media/java/android/media/session/SessionToken.aidl18
-rw-r--r--media/java/android/media/session/SessionToken.java (renamed from media/java/android/media/session/MediaSessionToken.java)26
-rw-r--r--media/java/android/media/session/TransportController.java4
-rw-r--r--media/java/android/media/session/TransportPerformer.java4
-rw-r--r--packages/DefaultContainerService/Android.mk2
-rw-r--r--packages/Keyguard/res/layout/keyguard_bouncer.xml8
-rw-r--r--packages/Keyguard/res/layout/keyguard_sim_pin_view.xml22
-rw-r--r--packages/Keyguard/res/layout/keyguard_sim_puk_view.xml22
-rw-r--r--packages/Keyguard/res/values-sw600dp/styles.xml21
-rw-r--r--packages/Keyguard/res/values/styles.xml5
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java2
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java10
-rw-r--r--packages/services/PacProcessor/Android.mk2
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindow.java2
-rw-r--r--rs/java/android/renderscript/ScriptIntrinsicResize.java112
-rw-r--r--rs/jni/android_renderscript_RenderScript.cpp44
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java23
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java19
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderProxy.java379
-rw-r--r--services/core/java/com/android/server/media/MediaRouteProviderWatcher.java229
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java319
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java146
-rw-r--r--services/core/java/com/android/server/media/RouteConnectionRecord.java108
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageManagerService.java32
-rw-r--r--tests/OneMedia/AndroidManifest.xml9
-rw-r--r--tests/OneMedia/res/layout/activity_one_player.xml6
-rw-r--r--tests/OneMedia/res/values/strings.xml1
-rw-r--r--tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl4
-rw-r--r--tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl4
-rw-r--r--tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java12
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerController.java21
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerService.java4
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerSession.java100
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java17
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java15
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java44
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java15
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/Renderer.java48
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java22
-rw-r--r--tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java15
-rw-r--r--tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java204
119 files changed, 5316 insertions, 869 deletions
diff --git a/Android.mk b/Android.mk
index adc9ef1..c2910fd 100644
--- a/Android.mk
+++ b/Android.mk
@@ -288,11 +288,14 @@ LOCAL_SRC_FILES += \
media/java/android/media/IRemoteDisplayProvider.aidl \
media/java/android/media/IRemoteVolumeObserver.aidl \
media/java/android/media/IRingtonePlayer.aidl \
- media/java/android/media/session/IMediaController.aidl \
- media/java/android/media/session/IMediaControllerCallback.aidl \
- media/java/android/media/session/IMediaSession.aidl \
- media/java/android/media/session/IMediaSessionCallback.aidl \
- media/java/android/media/session/IMediaSessionManager.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/session/ISessionController.aidl \
+ media/java/android/media/session/ISessionControllerCallback.aidl \
+ media/java/android/media/session/ISession.aidl \
+ media/java/android/media/session/ISessionCallback.aidl \
+ media/java/android/media/session/ISessionManager.aidl \
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6ee76fb..3573e6d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27,6 +27,7 @@ package android {
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";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
+ field public static final java.lang.String BIND_ROUTE_PROVIDER = "android.permission.BIND_ROUTE_PROVIDER";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
@@ -10071,6 +10072,7 @@ package android.graphics {
method public android.graphics.Xfermode getXfermode();
method public final boolean isAntiAlias();
method public final boolean isDither();
+ method public boolean isElegantTextHeight();
method public final boolean isFakeBoldText();
method public final boolean isFilterBitmap();
method public final boolean isLinearText();
@@ -10089,6 +10091,7 @@ package android.graphics {
method public void setColor(int);
method public android.graphics.ColorFilter setColorFilter(android.graphics.ColorFilter);
method public void setDither(boolean);
+ method public void setElegantTextHeight(boolean);
method public void setFakeBoldText(boolean);
method public void setFilterBitmap(boolean);
method public void setFlags(int);
@@ -12121,7 +12124,9 @@ package android.hardware.display {
public final class VirtualDisplay {
method public android.view.Display getDisplay();
+ method public android.view.Surface getSurface();
method public void release();
+ method public void setSurface(android.view.Surface);
}
}
@@ -14959,24 +14964,68 @@ package android.media.effect {
}
-package android.media.session {
+package android.media.routeprovider {
- 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 static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
- method public android.media.session.TransportController getTransportController();
- method public void removeCallback(android.media.session.MediaController.Callback);
- method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
- method public void sendMediaButton(int);
+ public final class RouteConnection {
+ ctor public RouteConnection(android.media.routeprovider.RouteProviderService, android.media.session.RouteInfo);
+ method public android.media.routeprovider.RouteInterfaceHandler addRouteInterface(java.lang.String);
+ method public android.media.routeprovider.RouteInterfaceHandler getRouteInterface(java.lang.String);
+ method public void shutDown();
}
- public static abstract class MediaController.Callback {
- ctor public MediaController.Callback();
- method public void onEvent(java.lang.String, android.os.Bundle);
- method public void onRouteChanged(android.os.Bundle);
+ public final class RouteInterfaceHandler {
+ method public void addListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener, android.os.Handler);
+ method public java.lang.String getName();
+ method public void removeListener(android.media.routeprovider.RouteInterfaceHandler.CommandListener);
+ method public void sendEvent(java.lang.String, android.os.Bundle);
+ method public static void sendResult(android.os.ResultReceiver, int, android.os.Bundle);
+ }
+
+ public static abstract class RouteInterfaceHandler.CommandListener {
+ ctor public RouteInterfaceHandler.CommandListener();
+ method public abstract boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
}
+ public final class RoutePlaybackControlsHandler {
+ method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
+ method public void addListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener, android.os.Handler);
+ method public static android.media.routeprovider.RoutePlaybackControlsHandler addTo(android.media.routeprovider.RouteConnection);
+ method public void removeListener(android.media.routeprovider.RoutePlaybackControlsHandler.Listener);
+ method public void sendPlaybackChangeEvent(int);
+ }
+
+ public static abstract class RoutePlaybackControlsHandler.Listener extends android.media.routeprovider.RouteInterfaceHandler.CommandListener {
+ ctor public RoutePlaybackControlsHandler.Listener();
+ method public boolean fastForward();
+ method public long getCapabilities();
+ method public long getCurrentPosition();
+ method public final boolean onCommand(android.media.routeprovider.RouteInterfaceHandler, java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public boolean pause();
+ method public void playNow(java.lang.String, android.os.ResultReceiver);
+ method public boolean resume();
+ }
+
+ public abstract class RouteProviderService extends android.app.Service {
+ ctor public RouteProviderService();
+ method public abstract android.media.routeprovider.RouteConnection connect(android.media.session.RouteInfo, android.media.routeprovider.RouteRequest);
+ method public abstract java.util.List<android.media.session.RouteInfo> getMatchingRoutes(java.util.List<android.media.routeprovider.RouteRequest>);
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void updateDiscoveryRequests(java.util.List<android.media.routeprovider.RouteRequest>);
+ field public static final java.lang.String SERVICE_INTERFACE = "com.android.media.session.MediaRouteProvider";
+ }
+
+ public final class RouteRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.media.session.RouteOptions getConnectionOptions();
+ method public android.media.session.SessionInfo getSessionInfo();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+}
+
+package android.media.session {
+
public final class MediaMetadata implements android.os.Parcelable {
method public int describeContents();
method public android.graphics.Bitmap getBitmap(java.lang.String);
@@ -15017,36 +15066,6 @@ package android.media.session {
method public android.media.session.MediaMetadata.Builder putString(java.lang.String, java.lang.String);
}
- public final class MediaSession {
- method public void addCallback(android.media.session.MediaSession.Callback);
- method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
- method public android.media.session.MediaSessionToken getSessionToken();
- method public android.media.session.TransportPerformer getTransportPerformer();
- method public void publish();
- method public void release();
- method public void removeCallback(android.media.session.MediaSession.Callback);
- method public void sendEvent(java.lang.String, android.os.Bundle);
- method public android.media.session.TransportPerformer setTransportPerformerEnabled();
- }
-
- public static abstract class MediaSession.Callback {
- ctor public MediaSession.Callback();
- method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
- method public void onMediaButton(android.content.Intent);
- method public void onRequestRouteChange(android.os.Bundle);
- }
-
- public final class MediaSessionManager {
- method public android.media.session.MediaSession createSession(java.lang.String);
- method public java.util.List<android.media.session.MediaController> getActiveSessions();
- }
-
- public class MediaSessionToken implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator CREATOR;
- }
-
public final class PlaybackState implements android.os.Parcelable {
ctor public PlaybackState();
ctor public PlaybackState(android.media.session.PlaybackState);
@@ -15075,6 +15094,7 @@ package android.media.session {
field public static final long ACTION_STOP = 1L; // 0x1L
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int PLAYSTATE_BUFFERING = 6; // 0x6
+ field public static final int PLAYSTATE_CONNECTING = 8; // 0x8
field public static final int PLAYSTATE_ERROR = 7; // 0x7
field public static final int PLAYSTATE_FAST_FORWARDING = 4; // 0x4
field public static final int PLAYSTATE_NONE = 0; // 0x0
@@ -15084,11 +15104,44 @@ package android.media.session {
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
}
+ public final class Route {
+ method public android.media.session.RouteInterface getInterface(java.lang.String);
+ method public android.media.session.RouteOptions getOptions();
+ method public android.media.session.RouteInfo getRouteInfo();
+ }
+
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.util.List<android.media.session.RouteOptions> getConnectionMethods();
+ method public java.lang.String getId();
+ method public java.lang.String getName();
+ method public java.lang.String getProvider();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static final class RouteInfo.Builder {
+ ctor public RouteInfo.Builder(android.media.session.RouteInfo);
+ ctor public RouteInfo.Builder();
+ method public android.media.session.RouteInfo.Builder addRouteOptions(android.media.session.RouteOptions);
+ method public android.media.session.RouteInfo build();
+ method public android.media.session.RouteInfo.Builder clearRouteOptions();
+ method public int getOptionsSize();
+ method public android.media.session.RouteInfo.Builder setId(java.lang.String);
+ method public android.media.session.RouteInfo.Builder setName(java.lang.String);
+ }
+
public final class RouteInterface {
method public void addListener(android.media.session.RouteInterface.EventListener);
method public void addListener(android.media.session.RouteInterface.EventListener, android.os.Handler);
method public void removeListener(android.media.session.RouteInterface.EventListener);
- method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public boolean sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ field public static final int RESULT_COMMAND_NOT_SUPPORTED = -3; // 0xfffffffd
+ field public static final int RESULT_ERROR = -1; // 0xffffffff
+ field public static final int RESULT_INTERFACE_NOT_SUPPORTED = -2; // 0xfffffffe
+ field public static final int RESULT_NOT_CONNECTED = -5; // 0xfffffffb
+ field public static final int RESULT_ROUTE_IS_STALE = -4; // 0xfffffffc
+ field public static final int RESULT_SUCCESS = 1; // 0x1
}
public static abstract class RouteInterface.EventListener {
@@ -15096,40 +15149,100 @@ package android.media.session {
method public abstract void onEvent(java.lang.String, android.os.Bundle);
}
- public static abstract class RouteInterface.Stub {
- ctor public RouteInterface.Stub();
- method public abstract java.lang.String getName();
- method public abstract void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
- method public final void sendEvent(android.media.session.MediaSession, java.lang.String, android.os.Bundle);
+ public final class RouteOptions implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getConnectionParams();
+ method public java.util.List<java.lang.String> getInterfaceNames();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
}
- public final class RouteTransportControls {
- method public void addListener(android.media.session.RouteTransportControls.Listener);
- method public void addListener(android.media.session.RouteTransportControls.Listener, android.os.Handler);
- method public void fastForward(float);
- method public static android.media.session.RouteTransportControls from(android.media.session.MediaController);
+ public static final class RouteOptions.Builder {
+ ctor public RouteOptions.Builder();
+ method public android.media.session.RouteOptions.Builder addInterface(java.lang.String);
+ method public android.media.session.RouteOptions build();
+ method public android.media.session.RouteOptions.Builder setParameters(android.os.Bundle);
+ }
+
+ public final class RoutePlaybackControls {
+ method public void addListener(android.media.session.RoutePlaybackControls.Listener);
+ method public void addListener(android.media.session.RoutePlaybackControls.Listener, android.os.Handler);
+ method public void fastForward();
+ method public static android.media.session.RoutePlaybackControls from(android.media.session.Route);
method public void getCapabilities(android.os.ResultReceiver);
method public void getCurrentPosition(android.os.ResultReceiver);
method public void pause();
- method public void play();
- method public void removeListener(android.media.session.RouteTransportControls.Listener);
- field public static final java.lang.String NAME = "android.media.session.RouteTransportControls";
+ method public void playNow(java.lang.String);
+ method public void removeListener(android.media.session.RoutePlaybackControls.Listener);
+ method public void resume();
+ field public static final java.lang.String NAME = "android.media.session.RoutePlaybackControls";
}
- public static abstract class RouteTransportControls.Listener {
- ctor public RouteTransportControls.Listener();
- method public void onMetadataUpdate(android.os.Bundle);
+ public static abstract class RoutePlaybackControls.Listener extends android.media.session.RouteInterface.EventListener {
+ ctor public RoutePlaybackControls.Listener();
+ method public final void onEvent(java.lang.String, android.os.Bundle);
+ method public void onMetadataUpdate(android.media.session.MediaMetadata);
method public void onPlaybackStateChange(int);
}
- public static abstract class RouteTransportControls.Stub extends android.media.session.RouteInterface.Stub {
- ctor public RouteTransportControls.Stub(android.media.session.MediaSession);
- method public void fastForward(float);
- method public long getCapabilities();
- method public long getCurrentPosition();
- method public java.lang.String getName();
+ public final class Session {
+ method public void addCallback(android.media.session.Session.Callback);
+ method public void addCallback(android.media.session.Session.Callback, android.os.Handler);
+ method public void connect(android.media.session.RouteInfo, android.media.session.RouteOptions);
+ method public void disconnect(android.media.session.RouteInfo);
+ method public android.media.session.SessionToken getSessionToken();
+ method public android.media.session.TransportPerformer getTransportPerformer();
+ method public void publish();
+ method public void release();
+ method public void removeCallback(android.media.session.Session.Callback);
+ method public void sendEvent(java.lang.String, android.os.Bundle);
+ method public void setRouteOptions(java.util.List<android.media.session.RouteOptions>);
+ method public android.media.session.TransportPerformer setTransportPerformerEnabled();
+ }
+
+ public static abstract class Session.Callback {
+ ctor public Session.Callback();
method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
- method public final void updatePlaybackState(int);
+ method public void onMediaButton(android.content.Intent);
+ method public void onRequestRouteChange(android.media.session.RouteInfo);
+ method public void onRouteConnected(android.media.session.Route);
+ method public void onRouteDisconnected(android.media.session.Route, int);
+ }
+
+ public final class SessionController {
+ method public void addCallback(android.media.session.SessionController.Callback);
+ method public void addCallback(android.media.session.SessionController.Callback, android.os.Handler);
+ method public static android.media.session.SessionController fromToken(android.media.session.SessionToken);
+ method public android.media.session.TransportController getTransportController();
+ method public void removeCallback(android.media.session.SessionController.Callback);
+ method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void sendMediaButton(int);
+ method public void showRoutePicker();
+ }
+
+ public static abstract class SessionController.Callback {
+ ctor public SessionController.Callback();
+ method public void onEvent(java.lang.String, android.os.Bundle);
+ method public void onRouteChanged(android.media.session.RouteInfo);
+ }
+
+ public final class SessionInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getId();
+ method public java.lang.String getPackageName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public final class SessionManager {
+ method public android.media.session.Session createSession(java.lang.String);
+ method public java.util.List<android.media.session.SessionController> getActiveSessions();
+ }
+
+ public class SessionToken implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
}
public final class TransportController {
@@ -24357,6 +24470,15 @@ package android.renderscript {
method public void setRed(int, int);
}
+ public final class ScriptIntrinsicResize extends android.renderscript.ScriptIntrinsic {
+ method public static android.renderscript.ScriptIntrinsicResize create(android.renderscript.RenderScript);
+ method public void forEach_bicubic(android.renderscript.Allocation);
+ method public void forEach_bicubic(android.renderscript.Allocation, android.renderscript.Script.LaunchOptions);
+ method public android.renderscript.Script.FieldID getFieldID_Input();
+ method public android.renderscript.Script.KernelID getKernelID_bicubic();
+ method public void setInput(android.renderscript.Allocation);
+ }
+
public final class ScriptIntrinsicYuvToRGB extends android.renderscript.ScriptIntrinsic {
method public static android.renderscript.ScriptIntrinsicYuvToRGB create(android.renderscript.RenderScript, android.renderscript.Element);
method public void forEach(android.renderscript.Allocation);
@@ -32617,7 +32739,8 @@ package android.webkit {
method public void clearSslPreferences();
method public deprecated void clearView();
method public android.webkit.WebBackForwardList copyBackForwardList();
- method public android.print.PrintDocumentAdapter createPrintDocumentAdapter();
+ method public deprecated android.print.PrintDocumentAdapter createPrintDocumentAdapter();
+ method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String);
method public void destroy();
method public void documentHasImages(android.os.Message);
method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>);
@@ -34970,6 +35093,7 @@ package android.widget {
method public void setCursorVisible(boolean);
method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback);
method public final void setEditableFactory(android.text.Editable.Factory);
+ method public void setElegantTextHeight(boolean);
method public void setEllipsize(android.text.TextUtils.TruncateAt);
method public void setEms(int);
method public void setError(java.lang.CharSequence);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 77b5485..f1ce54a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -67,7 +67,7 @@ import android.location.ILocationManager;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
-import android.media.session.MediaSessionManager;
+import android.media.session.SessionManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyManager;
@@ -639,7 +639,7 @@ class ContextImpl extends Context {
registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new MediaSessionManager(ctx);
+ return new SessionManager(ctx);
}
});
registerService(TRUST_SERVICE, new ServiceFetcher() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ff92d82..906484a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2387,10 +2387,10 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.media.session.MediaSessionManager} for managing media Sessions.
+ * {@link android.media.session.SessionManager} for managing media Sessions.
*
* @see #getSystemService
- * @see android.media.session.MediaSessionManager
+ * @see android.media.session.SessionManager
*/
public static final String MEDIA_SESSION_SERVICE = "media_session";
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 2c53f03..bb290af 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -570,6 +570,14 @@ public interface CameraDevice extends AutoCloseable {
public static abstract class CaptureListener {
/**
+ * This constant is used to indicate that no images were captured for
+ * the request.
+ *
+ * @hide
+ */
+ public static final int NO_FRAMES_CAPTURED = -1;
+
+ /**
* This method is called when the camera device has started capturing
* the output image for the request, at the beginning of image exposure.
*
@@ -693,9 +701,12 @@ public interface CameraDevice extends AutoCloseable {
* The CameraDevice sending the callback.
* @param sequenceId
* A sequence ID returned by the {@link #capture} family of functions.
- * @param frameNumber
+ * @param lastFrameNumber
* The last frame number (returned by {@link CaptureResult#getFrameNumber}
* or {@link CaptureFailure#getFrameNumber}) in the capture sequence.
+ * The last frame number may be equal to NO_FRAMES_CAPTURED if no images
+ * were captured for this sequence. This can happen, for example, when a
+ * repeating request or burst is cleared right after being set.
*
* @see CaptureResult#getFrameNumber()
* @see CaptureFailure#getFrameNumber()
@@ -703,7 +714,7 @@ public interface CameraDevice extends AutoCloseable {
* @see CaptureFailure#getSequenceId()
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
- int sequenceId, int frameNumber) {
+ int sequenceId, int lastFrameNumber) {
// default empty implementation
}
}
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 70d3c63..d8981c8 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2134,8 +2134,8 @@ public final class CaptureResult extends CameraMetadata {
* @see #SYNC_FRAME_NUMBER_UNKNOWN
* @hide
*/
- public static final Key<Integer> SYNC_FRAME_NUMBER =
- new Key<Integer>("android.sync.frameNumber", int.class);
+ public static final Key<Long> SYNC_FRAME_NUMBER =
+ new Key<Long>("android.sync.frameNumber", long.class);
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index cd44b51..7328fe3 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -292,6 +292,70 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
}
+ /**
+ * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
+ * starting and stopping repeating request and flushing.
+ *
+ * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
+ * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
+ * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
+ * is added to the list mFrameNumberRequestPairs.</p>
+ *
+ * @param requestId the request ID of the current repeating request.
+ *
+ * @param lastFrameNumber last frame number returned from binder.
+ */
+ private void checkEarlyTriggerSequenceComplete(
+ final int requestId, final long lastFrameNumber) {
+ // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
+ // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
+ if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
+ final CaptureListenerHolder holder;
+ int index = mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null;
+ if (holder != null) {
+ mCaptureListenerMap.removeAt(index);
+ }
+
+ if (holder != null) {
+ if (DEBUG) {
+ Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
+ + " request did not reach HAL");
+ }
+
+ Runnable resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "early trigger sequence complete for request %d",
+ requestId));
+ }
+ if (lastFrameNumber < Integer.MIN_VALUE
+ || lastFrameNumber > Integer.MAX_VALUE) {
+ throw new AssertionError(lastFrameNumber + " cannot be cast to int");
+ }
+ holder.getListener().onCaptureSequenceCompleted(
+ CameraDevice.this,
+ requestId,
+ (int)lastFrameNumber);
+ }
+ }
+ };
+ holder.getHandler().post(resultDispatch);
+ } else {
+ Log.w(TAG, String.format(
+ "did not register listener to request %d",
+ requestId));
+ }
+ } else {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber,
+ requestId));
+ }
+ }
+
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener,
Handler handler, boolean repeating) throws CameraAccessException {
@@ -313,7 +377,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
try {
requestId = mRemoteDevice.submitRequestList(requestList, repeating,
/*out*/lastFrameNumberRef);
- if (!repeating) {
+ if (DEBUG) {
Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
}
} catch (CameraRuntimeException e) {
@@ -322,25 +386,17 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
// impossible
return -1;
}
+
if (listener != null) {
mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
requestList, handler, repeating));
}
long lastFrameNumber = lastFrameNumberRef.getNumber();
- /**
- * If it's the first repeating request, then returned lastFrameNumber can be
- * negative. Otherwise, it should always be non-negative.
- */
- if (((lastFrameNumber < 0) && (requestId > 0))
- || ((lastFrameNumber < 0) && (!repeating))) {
- throw new AssertionError(String.format("returned bad frame number %d",
- lastFrameNumber));
- }
+
if (repeating) {
if (mRepeatingRequestId != REQUEST_ID_NONE) {
- mFrameNumberRequestPairs.add(
- new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
}
mRepeatingRequestId = requestId;
} else {
@@ -395,12 +451,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
LongParcelable lastFrameNumberRef = new LongParcelable();
mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
long lastFrameNumber = lastFrameNumberRef.getNumber();
- if ((lastFrameNumber < 0) && (requestId > 0)) {
- throw new AssertionError(String.format("returned bad frame number %d",
- lastFrameNumber));
- }
- mFrameNumberRequestPairs.add(
- new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
+
+ checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
+
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -443,11 +496,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
mRemoteDevice.flush(/*out*/lastFrameNumberRef);
if (mRepeatingRequestId != REQUEST_ID_NONE) {
long lastFrameNumber = lastFrameNumberRef.getNumber();
- if (lastFrameNumber < 0) {
- Log.e(TAG, String.format("returned bad frame number %d", lastFrameNumber));
- }
- mFrameNumberRequestPairs.add(
- new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
mRepeatingRequestId = REQUEST_ID_NONE;
}
} catch (CameraRuntimeException e) {
@@ -582,8 +631,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
*/
if (frameNumber != mCompletedFrameNumber + 1) {
throw new AssertionError(String.format(
- "result frame number %d comes out of order",
- frameNumber));
+ "result frame number %d comes out of order, should be %d + 1",
+ frameNumber, mCompletedFrameNumber));
}
mCompletedFrameNumber++;
}
@@ -607,11 +656,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
final int requestId = frameNumberRequestPair.getValue();
final CaptureListenerHolder holder;
synchronized (mLock) {
- int index = CameraDevice.this.mCaptureListenerMap.indexOfKey(requestId);
- holder = (index >= 0) ? CameraDevice.this.mCaptureListenerMap.valueAt(index)
+ int index = mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
: null;
if (holder != null) {
- CameraDevice.this.mCaptureListenerMap.removeAt(index);
+ mCaptureListenerMap.removeAt(index);
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "remove holder for requestId %d, "
+ + "because lastFrame %d is <= %d",
+ requestId, frameNumberRequestPair.getKey(),
+ completedFrameNumber));
+ }
}
}
iter.remove();
@@ -628,11 +684,16 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
requestId));
}
+ long lastFrameNumber = frameNumberRequestPair.getKey();
+ if (lastFrameNumber < Integer.MIN_VALUE
+ || lastFrameNumber > Integer.MAX_VALUE) {
+ throw new AssertionError(lastFrameNumber
+ + " cannot be cast to int");
+ }
holder.getListener().onCaptureSequenceCompleted(
CameraDevice.this,
requestId,
- // TODO: this is problematic, crop long to int
- frameNumberRequestPair.getKey().intValue());
+ (int)lastFrameNumber);
}
}
};
@@ -705,6 +766,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
// Fire onCaptureSequenceCompleted
+ if (DEBUG) {
+ Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
+ }
mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
checkAndFireSequenceComplete();
@@ -766,18 +830,28 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
if (DEBUG) {
Log.d(TAG, "Received result for id " + requestId);
}
- final CaptureListenerHolder holder =
- CameraDevice.this.mCaptureListenerMap.get(requestId);
+ final CaptureListenerHolder holder;
+ synchronized (mLock) {
+ holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
+ }
Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
// Check if we have a listener for this
if (holder == null) {
+ if (DEBUG) {
+ Log.v(TAG, "holder is null, early return");
+ }
return;
}
- if (isClosed()) return;
+ if (isClosed()) {
+ if (DEBUG) {
+ Log.v(TAG, "camera is closed, early return");
+ }
+ return;
+ }
final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a517bc5..79673b3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -437,6 +437,14 @@ public final class DisplayManager {
* The behavior of the virtual display depends on the flags that are provided
* to this method. By default, virtual displays are created to be private,
* non-presentation and unsecure. Permissions may be required to use certain flags.
+ * </p><p>
+ * As of {@link android.os.Build.VERSION_CODES#KITKAT_WATCH}, the surface may
+ * be attached or detached dynamically using {@link VirtualDisplay#setSurface}.
+ * Previously, the surface had to be non-null when {@link #createVirtualDisplay}
+ * was called and could not be changed for the lifetime of the display.
+ * </p><p>
+ * Detaching the surface that backs a virtual display has a similar effect to
+ * turning off the screen.
* </p>
*
* @param name The name of the virtual display, must be non-empty.
@@ -444,7 +452,7 @@ public final class DisplayManager {
* @param height The height of the virtual display in pixels, must be greater than 0.
* @param densityDpi The density of the virtual display in dpi, must be greater than 0.
* @param surface The surface to which the content of the virtual display should
- * be rendered, must be non-null.
+ * be rendered, or null if there is none initially.
* @param flags A combination of virtual display flags:
* {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
* {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, or {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3417430..a8d55e8 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -377,9 +377,6 @@ public final class DisplayManagerGlobal {
throw new IllegalArgumentException("width, height, and densityDpi must be "
+ "greater than 0");
}
- if (surface == null) {
- throw new IllegalArgumentException("surface must not be null");
- }
Binder token = new Binder();
int displayId;
@@ -404,7 +401,15 @@ public final class DisplayManagerGlobal {
}
return null;
}
- return new VirtualDisplay(this, display, token);
+ return new VirtualDisplay(this, display, token, surface);
+ }
+
+ public void setVirtualDisplaySurface(IBinder token, Surface surface) {
+ try {
+ mDm.setVirtualDisplaySurface(token, surface);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to set virtual display surface.", ex);
+ }
}
public void releaseVirtualDisplay(IBinder token) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 68eb13f..23c58c8 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -63,5 +63,8 @@ interface IDisplayManager {
String name, int width, int height, int densityDpi, in Surface surface, int flags);
// No permissions required but must be same Uid as the creator.
+ void setVirtualDisplaySurface(in IBinder token, in Surface surface);
+
+ // No permissions required but must be same Uid as the creator.
void releaseVirtualDisplay(in IBinder token);
}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 01e5bac..691d6a0 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -17,15 +17,18 @@ package android.hardware.display;
import android.os.IBinder;
import android.view.Display;
+import android.view.Surface;
/**
* Represents a virtual display. The content of a virtual display is rendered to a
* {@link android.view.Surface} that you must provide to {@link DisplayManager#createVirtualDisplay
* createVirtualDisplay()}.
- * <p>Because a virtual display renders to a surface provided by the application, it will be
+ * <p>
+ * Because a virtual display renders to a surface provided by the application, it will be
* released automatically when the process terminates and all remaining windows on it will
- * be forcibly removed. However, you should also explicitly call {@link #release} when you're
- * done with it.
+ * be forcibly removed. However, you should also explicitly call {@link #release} when
+ * you're done with it.
+ * </p>
*
* @see DisplayManager#createVirtualDisplay
*/
@@ -33,11 +36,14 @@ public final class VirtualDisplay {
private final DisplayManagerGlobal mGlobal;
private final Display mDisplay;
private IBinder mToken;
+ private Surface mSurface;
- VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token) {
+ VirtualDisplay(DisplayManagerGlobal global, Display display, IBinder token,
+ Surface surface) {
mGlobal = global;
mDisplay = display;
mToken = token;
+ mSurface = surface;
}
/**
@@ -48,6 +54,32 @@ public final class VirtualDisplay {
}
/**
+ * Gets the surface that backs the virtual display.
+ */
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Sets the surface that backs the virtual display.
+ * <p>
+ * Detaching the surface that backs a virtual display has a similar effect to
+ * turning off the screen.
+ * </p><p>
+ * It is still the caller's responsibility to destroy the surface after it has
+ * been detached.
+ * </p>
+ *
+ * @param surface The surface to set, or null to detach the surface from the virtual display.
+ */
+ public void setSurface(Surface surface) {
+ if (mSurface != surface) {
+ mGlobal.setVirtualDisplaySurface(mToken, surface);
+ mSurface = surface;
+ }
+ }
+
+ /**
* Releases the virtual display and destroys its underlying surface.
* <p>
* All remaining windows on the virtual display will be forcibly removed
@@ -63,6 +95,7 @@ public final class VirtualDisplay {
@Override
public String toString() {
- return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken + "}";
+ return "VirtualDisplay{display=" + mDisplay + ", token=" + mToken
+ + ", surface=" + mSurface + "}";
}
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index ae24968..cfab1b3 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -109,14 +109,18 @@ public final class MediaStore {
* An intent to perform a search for music media and automatically play content from the
* result when possible. This can be fired, for example, by the result of a voice recognition
* command to listen to music.
- * <p>
- * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string
- * that can contain any type of unstructured music search, like the name of an artist,
- * an album, a song, a genre, or any combination of these.
- * <p>
- * Because this intent includes an open-ended unstructured search string, it makes the most
- * sense for apps that can support large-scale search of music, such as services connected
- * to an online database of music which can be streamed and played on the device.
+ * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}
+ * and {@link android.app.SearchManager#QUERY} extras. The
+ * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and
+ * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode.
+ * For more information about the search modes for this intent, see
+ * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based
+ * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common
+ * Intents</a>.</p>
+ *
+ * <p>This intent makes the most sense for apps that can support large-scale search of music,
+ * such as services connected to an online database of music which can be streamed and played
+ * on the device.</p>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index abd173a..2b81072 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -352,6 +352,7 @@ public final class Log {
/** @hide */ public static final int LOG_ID_RADIO = 1;
/** @hide */ public static final int LOG_ID_EVENTS = 2;
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
+ /** @hide */ public static final int LOG_ID_CRASH = 4;
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f44cc87..1f9ba46 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -720,6 +720,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static boolean sIgnoreMeasureCache = false;
/**
+ * Ignore the clipBounds of this view for the children.
+ */
+ static boolean sIgnoreClipBoundsForChildren = false;
+
+ /**
* This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
* calling setFlags.
*/
@@ -2963,7 +2968,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Current clip bounds. to which all drawing of this view are constrained.
*/
- private Rect mClipBounds = null;
+ Rect mClipBounds = null;
private boolean mLastIsOpaque;
@@ -3511,6 +3516,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// of whether a layout was requested on that View.
sIgnoreMeasureCache = targetSdkVersion < KITKAT;
+ // Older apps may need this to ignore the clip bounds
+ sIgnoreClipBoundsForChildren = targetSdkVersion < L;
+
sCompatibilityDone = true;
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index a64bdc7..9d4ffb1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2962,14 +2962,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- int saveCount = 0;
+ int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
+ boolean hasClipBounds = mClipBounds != null && !sIgnoreClipBoundsForChildren;
+ boolean clippingNeeded = clipToPadding || hasClipBounds;
+
+ if (clippingNeeded) {
+ clipSaveCount = canvas.save();
+ }
+
if (clipToPadding) {
- saveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
+ }
+ if (hasClipBounds) {
+ canvas.clipRect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
+ mClipBounds.bottom);
}
// We will draw our child's animation, let's reset the flag
@@ -3010,8 +3020,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
onDebugDraw(canvas);
}
- if (clipToPadding) {
- canvas.restoreToCount(saveCount);
+ if (clippingNeeded) {
+ canvas.restoreToCount(clipSaveCount);
}
// mGroupFlags might have been updated by drawChild()
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eec4354..4e4f37b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1173,7 +1173,15 @@ public final class ViewRootImpl implements ViewParent,
void dispatchApplyInsets(View host) {
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
- host.dispatchApplyWindowInsets(new WindowInsets(mFitSystemWindowsInsets));
+ boolean isRound = false;
+ if ((mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0
+ && mDisplay.getDisplayId() == 0) {
+ // we're fullscreen and not hosted in an ActivityView
+ isRound = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_windowIsRound);
+ }
+ host.dispatchApplyWindowInsets(new WindowInsets(
+ mFitSystemWindowsInsets, isRound));
}
private void performTraversals() {
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index f8cc793..2160efe 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -51,6 +51,11 @@ public class WindowInsets {
}
/** @hide */
+ public WindowInsets(Rect systemWindowInsets, boolean isRound) {
+ this(systemWindowInsets, EMPTY_RECT, isRound);
+ }
+
+ /** @hide */
public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, boolean isRound) {
mSystemWindowInsets = systemWindowInsets;
mWindowDecorInsets = windowDecorInsets;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 62fbbc4..d2e7324 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1136,9 +1136,18 @@ public class WebView extends AbsoluteLayout
}
/**
+ * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
+ * to provide a print document name.
+ */
+ @Deprecated
+ public PrintDocumentAdapter createPrintDocumentAdapter() {
+ checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter");
+ return mProvider.createPrintDocumentAdapter("default");
+ }
+
+ /**
* Creates a PrintDocumentAdapter that provides the content of this Webview for printing.
- * Only supported for API levels
- * {@link android.os.Build.VERSION_CODES#KITKAT} and above.
*
* The adapter works by converting the Webview contents to a PDF stream. The Webview cannot
* be drawn during the conversion process - any such draws are undefined. It is recommended
@@ -1146,11 +1155,14 @@ public class WebView extends AbsoluteLayout
* temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
* wrapped around the object returned and observing the onStart and onFinish methods. See
* {@link android.print.PrintDocumentAdapter} for more information.
+ *
+ * @param documentName The user-facing name of the printed document. See
+ * {@link android.print.PrintDocumentInfo}
*/
- public PrintDocumentAdapter createPrintDocumentAdapter() {
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
checkThread();
if (DebugFlags.TRACE_API) Log.d(LOGTAG, "createPrintDocumentAdapter");
- return mProvider.createPrintDocumentAdapter();
+ return mProvider.createPrintDocumentAdapter(documentName);
}
/**
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 9488cdd..5081ff5 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -148,7 +148,7 @@ public interface WebViewProvider {
public Picture capturePicture();
- public PrintDocumentAdapter createPrintDocumentAdapter();
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName);
public float getScale();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a7278da..b91111d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -652,6 +652,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean allCaps = false;
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
+ boolean elegant = false;
final Resources.Theme theme = context.getTheme();
@@ -728,6 +729,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextAppearance_shadowRadius:
r = appearance.getFloat(attr, 0);
break;
+
+ case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
+ elegant = appearance.getBoolean(attr, false);
+ break;
}
}
@@ -1065,6 +1070,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_textAllCaps:
allCaps = a.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextView_elegantTextHeight:
+ elegant = a.getBoolean(attr, false);
+ break;
}
}
a.recycle();
@@ -1245,6 +1254,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setHighlightColor(textColorHighlight);
}
setRawTextSize(textSize);
+ setElegantTextHeight(elegant);
if (allCaps) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -2468,6 +2478,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
}
+ if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) {
+ setElegantTextHeight(appearance.getBoolean(
+ com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
+ }
+
appearance.recycle();
}
@@ -2615,6 +2630,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set the TextView's elegant height metrics flag. This setting selects font
+ * variants that have not been compacted to fit Latin-based vertical
+ * metrics, and also increases top and bottom bounds to provide more space.
+ *
+ * @param elegant set the paint's elegant metrics flag.
+ */
+ public void setElegantTextHeight(boolean elegant) {
+ mTextPaint.setElegantTextHeight(elegant);
+ }
+
+ /**
* Sets the text color for all the states (normal, selected,
* focused) to be this color.
*
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 5538dca..4a26b4b 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -55,6 +55,11 @@ public class RuntimeInit {
private static final native void nativeFinishInit();
private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
+ private static int Clog_e(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_CRASH, Log.ERROR, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
/**
* Use this to log a message when a thread exits due to an uncaught
* exception. The framework catches these for the main threads, so
@@ -68,7 +73,7 @@ public class RuntimeInit {
mCrashing = true;
if (mApplicationObject == null) {
- Slog.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
+ Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
@@ -77,7 +82,7 @@ public class RuntimeInit {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
- Slog.e(TAG, message.toString(), e);
+ Clog_e(TAG, message.toString(), e);
}
// Bring up crash dialog, wait for it to be dismissed
@@ -85,9 +90,9 @@ public class RuntimeInit {
mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
} catch (Throwable t2) {
try {
- Slog.e(TAG, "Error reporting crash", t2);
+ Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
- // Even Slog.e() fails! Oh well.
+ // Even Clog_e() fails! Oh well.
}
} finally {
// Try everything to make sure this process goes away.
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index f77a389..08a88d1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -357,6 +357,24 @@ public:
obj->setPaintOptionsAndroid(paintOpts);
}
+ static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
+ NPE_CHECK_RETURN_ZERO(env, paint);
+ SkPaint* obj = GraphicsJNI::getNativePaint(env, paint);
+ SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
+ return paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant;
+ }
+
+ static void setElegantTextHeight(JNIEnv* env, jobject paint, jboolean aa) {
+ NPE_CHECK_RETURN_VOID(env, paint);
+ SkPaint* obj = GraphicsJNI::getNativePaint(env, paint);
+ SkPaintOptionsAndroid::FontVariant variant =
+ aa ? SkPaintOptionsAndroid::kElegant_Variant :
+ SkPaintOptionsAndroid::kDefault_Variant;
+ SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
+ paintOpts.setFontVariant(variant);
+ obj->setPaintOptionsAndroid(paintOpts);
+ }
+
static jfloat getTextSize(JNIEnv* env, jobject paint) {
NPE_CHECK_RETURN_ZERO(env, paint);
return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSize());
@@ -401,10 +419,30 @@ public:
return SkScalarToFloat(metrics.fDescent);
}
+ static SkScalar getMetricsInternal(SkPaint *paint, SkPaint::FontMetrics *metrics) {
+ const int kElegantTop = 2500;
+ const int kElegantBottom = -1000;
+ const int kElegantAscent = 1946;
+ const int kElegantDescent = -512;
+ const int kElegantLeading = 0;
+ SkScalar spacing = paint->getFontMetrics(metrics);
+ SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
+ if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
+ SkScalar size = paint->getTextSize();
+ metrics->fTop = -size * kElegantTop / 2048;
+ metrics->fBottom = -size * kElegantBottom / 2048;
+ metrics->fAscent = -size * kElegantAscent / 2048;
+ metrics->fDescent = -size * kElegantDescent / 2048;
+ metrics->fLeading = size * kElegantLeading / 2048;
+ spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
+ }
+ return spacing;
+ }
+
static jfloat getFontMetrics(JNIEnv* env, jobject paint, jobject metricsObj) {
NPE_CHECK_RETURN_ZERO(env, paint);
SkPaint::FontMetrics metrics;
- SkScalar spacing = GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+ SkScalar spacing = getMetricsInternal(GraphicsJNI::getNativePaint(env, paint), &metrics);
if (metricsObj) {
SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
@@ -421,7 +459,7 @@ public:
NPE_CHECK_RETURN_ZERO(env, paint);
SkPaint::FontMetrics metrics;
- GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics);
+ getMetricsInternal(GraphicsJNI::getNativePaint(env, paint), &metrics);
int ascent = SkScalarRoundToInt(metrics.fAscent);
int descent = SkScalarRoundToInt(metrics.fDescent);
int leading = SkScalarRoundToInt(metrics.fLeading);
@@ -894,6 +932,8 @@ static JNINativeMethod methods[] = {
{"native_getTextAlign","(J)I", (void*) SkPaintGlue::getTextAlign},
{"native_setTextAlign","(JI)V", (void*) SkPaintGlue::setTextAlign},
{"native_setTextLocale","(JLjava/lang/String;)V", (void*) SkPaintGlue::setTextLocale},
+ {"isElegantTextHeight","()Z", (void*) SkPaintGlue::isElegantTextHeight},
+ {"setElegantTextHeight","(Z)V", (void*) SkPaintGlue::setElegantTextHeight},
{"getTextSize","()F", (void*) SkPaintGlue::getTextSize},
{"setTextSize","(F)V", (void*) SkPaintGlue::setTextSize},
{"getTextScaleX","()F", (void*) SkPaintGlue::getTextScaleX},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 475e926..662af89 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -23,6 +23,7 @@
#include "JNIHelp.h"
#include <fcntl.h>
+#include <inttypes.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -334,7 +335,7 @@ public:
if (b == NULL) {
b = new JavaBBinder(env, obj);
mBinder = b;
- ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%d\n",
+ ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
}
@@ -697,9 +698,9 @@ void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
"Not allowed to write file descriptors here");
break;
default:
- ALOGE("Unknown binder error code. 0x%x", err);
+ ALOGE("Unknown binder error code. 0x%" PRIx32, err);
String8 msg;
- msg.appendFormat("Unknown binder error code. 0x%x", err);
+ msg.appendFormat("Unknown binder error code. 0x%" PRIx32, err);
// RemoteException is a checked exception, only throw from certain methods.
jniThrowException(env, canThrowRemoteException
? "android/os/RemoteException" : "java/lang/RuntimeException", msg.string());
@@ -733,7 +734,7 @@ static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz,
if (uid > 0 && uid < 999) {
// In Android currently there are no uids in this range.
char buf[128];
- sprintf(buf, "Restoring bad calling ident: 0x%Lx", token);
+ sprintf(buf, "Restoring bad calling ident: 0x%" PRIx64, token);
jniThrowException(env, "java/lang/IllegalStateException", buf);
return;
}
@@ -965,8 +966,8 @@ static bool push_eventlog_string(char** pos, const char* end, const char* str) {
jint len = strlen(str);
int space_needed = 1 + sizeof(len) + len;
if (end - *pos < space_needed) {
- ALOGW("not enough space for string. remain=%d; needed=%d",
- (end - *pos), space_needed);
+ ALOGW("not enough space for string. remain=%" PRIdPTR "; needed=%d",
+ end - *pos, space_needed);
return false;
}
**pos = EVENT_TYPE_STRING;
@@ -981,8 +982,8 @@ static bool push_eventlog_string(char** pos, const char* end, const char* str) {
static bool push_eventlog_int(char** pos, const char* end, jint val) {
int space_needed = 1 + sizeof(val);
if (end - *pos < space_needed) {
- ALOGW("not enough space for int. remain=%d; needed=%d",
- (end - *pos), space_needed);
+ ALOGW("not enough space for int. remain=%" PRIdPTR "; needed=%d",
+ end - *pos, space_needed);
return false;
}
**pos = EVENT_TYPE_INT;
@@ -1068,7 +1069,7 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
return JNI_FALSE;
}
- ALOGV("Java code calling transact on %p in Java object %p with code %d\n",
+ ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",
target, obj, code);
#if ENABLE_BINDER_SAMPLE
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b8fe0ff..57e845f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2071,6 +2071,14 @@
android:description="@string/permdesc_bindTvInput"
android:protectionLevel="signature|system" />
+ <!-- Must be required by a {@link android.media.routeprovider.RouteProviderService}
+ to ensure that only the system can interact with it.
+ -->
+ <permission android:name="android.permission.BIND_ROUTE_PROVIDER"
+ android:label="@string/permlab_bindRouteProvider"
+ android:description="@string/permdesc_bindRouteProvider"
+ android:protectionLevel="signature" />
+
<!-- Must be required by device administration receiver, to ensure that only the
system can interact with it. -->
<permission android:name="android.permission.BIND_DEVICE_ADMIN"
diff --git a/core/res/res/drawable/btn_borderless_quantum.xml b/core/res/res/drawable/btn_borderless_quantum.xml
index 2e3c515..69a891a 100644
--- a/core/res/res/drawable/btn_borderless_quantum.xml
+++ b/core/res/res/drawable/btn_borderless_quantum.xml
@@ -16,6 +16,7 @@
<touch-feedback xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/colorButtonPressed">
+ <item android:drawable="@color/transparent" />
<item android:id="@id/mask"
android:drawable="@drawable/btn_qntm_alpha" />
</touch-feedback>
diff --git a/core/res/res/drawable/btn_check_quantum_anim.xml b/core/res/res/drawable/btn_check_quantum_anim.xml
index d68d512..0600522 100644
--- a/core/res/res/drawable/btn_check_quantum_anim.xml
+++ b/core/res/res/drawable/btn_check_quantum_anim.xml
@@ -27,24 +27,27 @@
<group>
<path
- android:name="check"
- android:pathData="M 232.1,80.6 L 248.5,92.1 L 145.2,239.5 L 71.5,187.8 L 83,171.5 L 140.3,211.7 z"
- android:fill="?attr/colorControlActivated" />
+ android:name="box1"
+ android:pathData="M 240,80 L 240,240 L 80,240 L 80,80 L 240,80 L 240,80 z"
+ android:stroke="?attr/colorControlNormal"
+ android:strokeWidth="20"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round" />
</group>
<group>
<path
- android:name="box1"
- android:pathData="M 160,216.5 L 143.5,240 L 120,223.5 L 136.5,200 L 160,216.5 L 160,216.5 z"
- android:fill="?attr/colorControlActivated"
- android:stroke="?attr/colorControlActivated"
+ android:name="box2"
+ android:pathData="M 160,200 L 160,240 L 120,240 L 120,200 L 160,200 L 160,200 z"
+ android:stroke="?attr/colorControlNormal"
+ android:strokeWidth="10"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
<group>
<path
- android:name="box2"
+ android:name="box3"
android:pathData="M 160,216.5 L 143.5,240 L 120,223.5 L 136.5,200 L 160,216.5 L 160,216.5 z"
- android:rotation="-35"
+ android:rotation="35"
android:pivotX="140"
android:pivotY="220"
android:fill="?attr/colorControlNormal"
@@ -55,25 +58,22 @@
</group>
<group>
<path
- android:name="box3"
- android:pathData="M 160,200 L 160,240 L 120,240 L 120,200 L 160,200 L 160,200 z"
- android:stroke="?attr/colorControlNormal"
- android:strokeWidth="10"
+ android:name="box4"
+ android:pathData="M 160,216.5 L 143.5,240 L 120,223.5 L 136.5,200 L 160,216.5 L 160,216.5 z"
+ android:fill="?attr/colorControlActivated"
+ android:stroke="?attr/colorControlActivated"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
<group>
<path
- android:name="box4"
- android:pathData="M 240,80 L 240,240 L 80,240 L 80,80 L 240,80 L 240,80 z"
- android:stroke="?attr/colorControlNormal"
- android:strokeWidth="20"
- android:strokeLineCap="round"
- android:strokeLineJoin="round" />
+ android:name="check"
+ android:pathData="M 232.1,80.6 L 248.5,92.1 L 145.2,239.5 L 71.5,187.8 L 83,171.5 L 140.3,211.7 z"
+ android:fill="?attr/colorControlActivated" />
</group>
<animation
android:durations="300,100,0,300"
- android:sequence="check,box1,box2,box3,box4" />
+ android:sequence="box1,box2,box3,box4,check" />
</vector>
diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml
index 98b68797..537162a 100644
--- a/core/res/res/layout/alert_dialog_quantum.xml
+++ b/core/res/res/layout/alert_dialog_quantum.xml
@@ -91,32 +91,32 @@
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layoutDirection="locale"
- android:measureWithLargestChild="true">
+ android:layoutDirection="locale">
<Button android:id="@+id/button3"
+ style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="start"
android:layout_marginRight="8dip"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarButtonStyle" />
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
+ <View
+ android:layout_width="0dp"
+ android:layout_height="@dimen/alert_dialog_button_bar_height"
+ android:layout_weight="1"
+ android:visibility="invisible" />
<Button android:id="@+id/button2"
+ style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="end"
- android:layout_marginRight="8dip"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarButtonStyle" />
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
<Button android:id="@+id/button1"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_marginLeft="8dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarButtonStyle" />
+ android:minHeight="@dimen/alert_dialog_button_bar_height" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 53fed98..782066e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3428,6 +3428,8 @@
<attr name="shadowDy" format="float" />
<!-- Radius of the shadow. -->
<attr name="shadowRadius" format="float" />
+ <!-- Elegant text height, especially for less compacted complex script text. -->
+ <attr name="elegantTextHeight" format="boolean" />
</declare-styleable>
<declare-styleable name="TextClock">
<!-- Specifies the formatting pattern used to show the time and/or date
@@ -3719,6 +3721,8 @@
<attr name="textIsSelectable" />
<!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
<attr name="textAllCaps" />
+ <!-- Elegant text height, especially for less compacted complex script text. -->
+ <attr name="elegantTextHeight" />
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f549290..2df5dc1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1429,4 +1429,7 @@
2 - The device DOES NOT have a permanent menu key; ignore autodetection. -->
<integer name="config_overrideHasPermanentMenuKey">0</integer>
+ <!-- default window inset isRound property -->
+ <bool name="config_windowIsRound">false</bool>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b0e1150..cacb41f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1077,6 +1077,12 @@
interface of a widget 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_bindRouteProvider">bind to a route provider 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_bindRouteProvider">Allows the holder to bind to any registered
+ route providers. 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_bindDeviceAdmin">interact with a device admin</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_bindDeviceAdmin">Allows the holder to send intents to
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index e42703e..bdc7ad0 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -97,6 +97,7 @@ please see styles_device_defaults.xml.
<item name="textColorLink">?textColorLink</item>
<item name="textSize">@dimen/text_size_body_1_quantum</item>
<item name="fontFamily">@string/font_family_body_1_quantum</item>
+ <item name="elegantTextHeight">true</item>
</style>
<style name="TextAppearance.Quantum.Display4">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b0f19ec..03c617a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -291,6 +291,7 @@
<java-symbol type="bool" name="config_wifi_batched_scan_supported" />
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
+ <java-symbol type="bool" name="config_windowIsRound" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index a28496e..c2e31f4 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -317,7 +317,7 @@ please see themes_device_defaults.xml.
<item name="dividerVertical">?attr/listDivider</item>
<item name="dividerHorizontal">?attr/listDivider</item>
<item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar</item>
- <item name="buttonBarButtonStyle">?attr/borderlessButtonStyle</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item>
<item name="segmentedButtonStyle">@style/Widget.Quantum.SegmentedButton</item>
<!-- SearchView attributes -->
@@ -662,7 +662,7 @@ please see themes_device_defaults.xml.
<item name="dividerVertical">?attr/listDivider</item>
<item name="dividerHorizontal">?attr/listDivider</item>
<item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item>
- <item name="buttonBarButtonStyle">?attr/borderlessButtonStyle</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
<item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item>
<!-- SearchView attributes -->
diff --git a/docs/html/design/media/dialogs_examples.png b/docs/html/design/media/dialogs_examples.png
index c136476..6ffcee2 100644
--- a/docs/html/design/media/dialogs_examples.png
+++ b/docs/html/design/media/dialogs_examples.png
Binary files differ
diff --git a/docs/html/design/media/navigation_drawer_titles_icons.png b/docs/html/design/media/navigation_drawer_titles_icons.png
index 7cf1e0c..902a72d 100644
--- a/docs/html/design/media/navigation_drawer_titles_icons.png
+++ b/docs/html/design/media/navigation_drawer_titles_icons.png
Binary files differ
diff --git a/docs/html/design/media/selection_adjusting_actions.png b/docs/html/design/media/selection_adjusting_actions.png
index 0799b6b..32a7fec 100644
--- a/docs/html/design/media/selection_adjusting_actions.png
+++ b/docs/html/design/media/selection_adjusting_actions.png
Binary files differ
diff --git a/docs/html/design/media/touch_feedback_communication.png b/docs/html/design/media/touch_feedback_communication.png
index f8162d0..1d4a9dc 100644
--- a/docs/html/design/media/touch_feedback_communication.png
+++ b/docs/html/design/media/touch_feedback_communication.png
Binary files differ
diff --git a/docs/html/design/media/ui_overview_notifications.png b/docs/html/design/media/ui_overview_notifications.png
index 6043412..7975657 100644
--- a/docs/html/design/media/ui_overview_notifications.png
+++ b/docs/html/design/media/ui_overview_notifications.png
Binary files differ
diff --git a/docs/html/guide/components/intents-common.jd b/docs/html/guide/components/intents-common.jd
index 826dcff..a0f7ce1 100644
--- a/docs/html/guide/components/intents-common.jd
+++ b/docs/html/guide/components/intents-common.jd
@@ -56,6 +56,7 @@ page.tags="IntentFilter"
<li><a href="#Music">Music or Video</a>
<ol>
<li><a href="#PlayMedia">Play a media file</a></li>
+ <li><a href="#PlaySearch">Play music based on a search query</a></li>
</ol>
</li>
<li><a href="#Phone">Phone</a>
@@ -1287,8 +1288,250 @@ public void playMedia(Uri file) {
</pre>
+<h3 id="PlaySearch">Play music based on a search query</h3>
+<p>To play music based on a search query, use the
+{@link android.provider.MediaStore#INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH} intent. An app may fire
+this intent in response to the user's voice command to play music. The receiving app for this
+intent performs a search within its inventory to match existing content to the given query and
+starts playing that content.</p>
+<p>This intent should include the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} string
+extra, which specifies the inteded search mode. For example, the search mode can specify whether
+the search is for an artist name or song name.</p>
+
+<dl>
+<dt><b>Action</b></dt>
+<dd>{@link android.provider.MediaStore#INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH}</dd>
+
+<dt><b>Data URI Scheme</b></dt>
+<dd>None</dd>
+
+<dt><b>MIME Type</b></dt>
+<dd>None</dd>
+
+<dt><b>Extras</b></dt>
+<dd>
+<dl>
+<dt>{@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS MediaStore.EXTRA_MEDIA_FOCUS} (required)</dt>
+<dd>
+<p>Indicates the search mode (whether the user is looking for a particular artist, album, song,
+playlist, or radio channel). Most search modes take additional extras. For example, if the user
+is interested in listening to a particular song, the intent might have three additional extras:
+the song title, the artist, and the album. This intent supports the following search modes for
+each value of {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}:</p>
+<dl>
+<dt><p><em>Any</em> - <code>"vnd.android.cursor.item/*"</p></code></dt>
+<dd>
+<p>Play any music. The receiving app should play some music based on a smart choice, such
+as the last playlist the user listened to.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.app.SearchManager#QUERY} (required) - An empty string. This extra is always
+ provided for backward compatibility: existing apps that do not know about search modes can
+ process this intent as an unstructured search.</li>
+</ul>
+</dd>
+<dt><p><em>Unstructured</em> - <code>"vnd.android.cursor.item/*"</code></p></dt>
+<dd>
+<p>Play a particular song, album or genre from an unstructured search query. Apps may generate
+an intent with this search mode when they can't identify the type of content the user wants to
+listen to. Apps should use more specific search modes when possible.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination
+ of: the artist, the album, the song name, or the genre.</li>
+</ul>
+</dd>
+<dt><p><em>Genre</em> -
+{@link android.provider.MediaStore.Audio.Genres#ENTRY_CONTENT_TYPE Audio.Genres.ENTRY_CONTENT_TYPE}</p></dt>
+<dd>
+<p>Play music of a particular genre.</p>
+<p>Additional extras:</p>
+<ul>
+ <li><code>"android.intent.extra.genre"</code> (required) - The genre.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - The genre. This extra is always provided
+ for backward compatibility: existing apps that do not know about search modes can process
+ this intent as an unstructured search.</li>
+</ul>
+</dd>
+<dt><p><em>Artist</em> -
+{@link android.provider.MediaStore.Audio.Artists#ENTRY_CONTENT_TYPE Audio.Artists.ENTRY_CONTENT_TYPE}</p></dt>
+<dd>
+<p>Play music from a particular artist.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ARTIST} (required) - The artist.</li>
+ <li><code>"android.intent.extra.genre"</code> - The genre.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination of
+ the artist or the genre. This extra is always provided for backward compatibility:
+ existing apps that do not know about search modes can process this intent as an unstructured
+ search.</li>
+</ul>
+</dd>
+<dt><p><em>Album</em> -
+{@link android.provider.MediaStore.Audio.Albums#ENTRY_CONTENT_TYPE Audio.Albums.ENTRY_CONTENT_TYPE}</p></dt>
+<dd>
+<p>Play music from a particular album.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ALBUM} (required) - The album.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ARTIST} - The artist.</li>
+ <li><code>"android.intent.extra.genre"</code> - The genre.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination of
+ the album or the artist. This extra is always provided for backward
+ compatibility: existing apps that do not know about search modes can process this intent as an
+ unstructured search.</li>
+</ul>
+</dd>
+<dt><p><em>Song</em> - <code>"vnd.android.cursor.item/audio"</code></p></dt>
+<dd>
+<p>Play a particular song.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ALBUM} - The album.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ARTIST} - The artist.</li>
+ <li><code>"android.intent.extra.genre"</code> - The genre.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_TITLE} (required) - The song name.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination of:
+ the album, the artist, the genre, or the title. This extra is always provided for
+ backward compatibility: existing apps that do not know about search modes can process this
+ intent as an unstructured search.</li>
+</ul>
+</dd>
+<dt><p><em>Radio channel</em> - <code>"vnd.android.cursor.item/radio"</code></p></dt>
+<dd>
+<p>Play a particular radio channel or a radio channel that matches some criteria specified
+by additional extras.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ALBUM} - The album.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ARTIST} - The artist.</li>
+ <li><code>"android.intent.extra.genre"</code> - The genre.</li>
+ <li><code>"android.intent.extra.radio_channel"</code> - The radio channel.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_TITLE} - The song name that the radio
+ channel is based on.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination
+ of: the album, the artist, the genre, the radio channel, or the title. This extra is
+ always provided for backward compatibility: existing apps that do not know about search
+ modes can process this intent as an unstructured search.</li>
+</ul>
+</dd>
+<dt><p><em>Playlist</em> - {@link android.provider.MediaStore.Audio.Playlists#ENTRY_CONTENT_TYPE Audio.Playlists.ENTRY_CONTENT_TYPE}</p></dt>
+<dd>
+<p>Play a particular playlist or a playlist that matches some criteria specified
+by additional extras.</p>
+<p>Additional extras:</p>
+<ul>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ALBUM} - The album.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_ARTIST} - The artist.</li>
+ <li><code>"android.intent.extra.genre"</code> - The genre.</li>
+ <li><code>"android.intent.extra.playlist"</code> - The playlist.</li>
+ <li>{@link android.provider.MediaStore#EXTRA_MEDIA_TITLE} - The song name that the playlist is
+ based on.</li>
+ <li>{@link android.app.SearchManager#QUERY} (required) - A string that contains any combination
+ of: the album, the artist, the genre, the playlist, or the title. This extra is always
+ provided for backward compatibility: existing apps that do not know about search modes can
+ process this intent as an unstructured search.</li>
+</ul>
+</dd>
+</dl>
+</dd>
+</dl>
+</dd>
+</dl>
+
+
+
+<p><b>Example intent:</b></p>
+<p>If the user wants to listen to a radio station that plays songs from a particular artist,
+a search app may generate the following intent:</p>
+<pre>
+public void playSearchRadioByArtist(String artist) {
+ Intent intent = new Intent(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH);
+ intent.putExtra(MediaStore.EXTRA_MEDIA_FOCUS,
+ "vnd.android.cursor.item/radio");
+ intent.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist);
+ intent.putExtra(SearchManager.QUERY, artist);
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ startActivity(intent);
+ }
+}
+</pre>
+
+<p><b>Example intent filter:</b></p>
+<pre>
+&lt;activity ...>
+ &lt;intent-filter>
+ &lt;action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
+ &lt;category android:name="android.intent.category.DEFAULT" />
+ &lt;/intent-filter>
+&lt;/activity>
+</pre>
+<p>When handling this intent, your activity should check the value of the
+{@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra in the incoming
+{@link android.content.Intent} to determine the search mode. Once your activity has identified
+the search mode, it should read the values of the additional extras for that particular search mode.
+With this information your app can then perform the search within its inventory to play the
+content that matches the search query. For example:</p>
+<pre>
+protected void onCreate(Bundle savedInstanceState) {
+ ...
+ Intent intent = this.getIntent();
+ if (intent.getAction().compareTo(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) == 0) {
+
+ String mediaFocus = intent.getStringExtra(MediaStore.EXTRA_MEDIA_FOCUS);
+ String query = intent.getStringExtra(SearchManager.QUERY);
+
+ // Some of these extras may not be available depending on the search mode
+ String album = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ALBUM);
+ String artist = intent.getStringExtra(MediaStore.EXTRA_MEDIA_ARTIST);
+ String genre = intent.getStringExtra("android.intent.extra.genre");
+ String playlist = intent.getStringExtra("android.intent.extra.playlist");
+ String rchannel = intent.getStringExtra("android.intent.extra.radio_channel");
+ String title = intent.getStringExtra(MediaStore.EXTRA_MEDIA_TITLE);
+
+ // Determine the search mode and use the corresponding extras
+ if (mediaFocus == null) {
+ // 'Unstructured' search mode (backward compatible)
+ playUnstructuredSearch(query);
+
+ } else if (mediaFocus.compareTo("vnd.android.cursor.item/*") == 0) {
+ if (query.isEmpty()) {
+ // 'Any' search mode
+ playResumeLastPlaylist();
+ } else {
+ // 'Unstructured' search mode
+ playUnstructuredSearch(query);
+ }
+
+ } else if (mediaFocus.compareTo(MediaStore.Audio.Genres.ENTRY_CONTENT_TYPE) == 0) {
+ // 'Genre' search mode
+ playGenre(genre);
+
+ } else if (mediaFocus.compareTo(MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) == 0) {
+ // 'Artist' search mode
+ playArtist(artist, genre);
+
+ } else if (mediaFocus.compareTo(MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) == 0) {
+ // 'Album' search mode
+ playAlbum(album, artist);
+
+ } else if (mediaFocus.compareTo("vnd.android.cursor.item/audio") == 0) {
+ // 'Song' search mode
+ playSong(album, artist, genre, title);
+
+ } else if (mediaFocus.compareTo("vnd.android.cursor.item/radio") == 0) {
+ // 'Radio channel' search mode
+ playRadioChannel(album, artist, genre, rchannel, title);
+
+ } else if (mediaFocus.compareTo(MediaStore.Audio.Playlists.ENTRY_CONTENT_TYPE) == 0) {
+ // 'Playlist' search mode
+ playPlaylist(album, artist, genre, playlist, title);
+ }
+ }
+}
+</pre>
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index a6a6460..659e9f2 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -121,13 +121,13 @@ $("#icon-video-close").on("click", function() {
</p>
</div>
<div class="col-3-wide">
- <img src="/wear/images/screens/circle_message2.png" alt="Image of a Hangouts message">
+ <img src="/wear/images/screens/circle_message2.png" itemprop="image" alt="" >
<p class="wear-small">
Get glanceable, actionable information at just the right time throughout the day.
</p>
</div>
<div class="col-3-wide">
- <img src="/wear/images/screens/fitness-24.png" alt="Image showing ">
+ <img src="/wear/images/screens/fitness-24.png" alt="">
<p class="wear-small">
A wide range of sensors is available to your applications, from accelerometers to heart rate monitors.
</p>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 916cb5a..1e1128e 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -500,6 +500,7 @@ public class Paint {
mBidiFlags = BIDI_DEFAULT_LTR;
setTextLocale(Locale.getDefault());
+ setElegantTextHeight(false);
}
/**
@@ -1221,6 +1222,22 @@ public class Paint {
}
/**
+ * Get the elegant metrics flag.
+ *
+ * @return true if elegant metrics are enabled for text drawing.
+ */
+ public native boolean isElegantTextHeight();
+
+ /**
+ * Set the paint's elegant height metrics flag. This setting selects font
+ * variants that have not been compacted to fit Latin-based vertical
+ * metrics, and also increases top and bottom bounds to provide more space.
+ *
+ * @param elegant set the paint's elegant metrics flag for drawing text.
+ */
+ public native void setElegantTextHeight(boolean elegant);
+
+ /**
* Return the paint's text size.
*
* @return the paint's text size.
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 3773a49..5f59467 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -44,6 +44,8 @@ import java.io.IOException;
* Documentation pending.
*/
public class TouchFeedbackDrawable extends LayerDrawable {
+ private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
+
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
@@ -397,7 +399,7 @@ public class TouchFeedbackDrawable extends LayerDrawable {
if (mask != null && drewRipples) {
// TODO: This will also mask the lower layer, which is bad.
canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(mState.mTintXfermode), 0);
+ bounds.bottom, getMaskingPaint(DST_IN), 0);
mask.draw(canvas);
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 736b143..0992717 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -182,14 +182,14 @@ public class VectorDrawable extends Drawable {
public VectorDrawable() {
mVectorState = new VectorDrawableState(null);
- mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1);
+ mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 0);
setDuration(DEFAULT_DURATION);
}
private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
mVectorState = new VectorDrawableState(state);
- mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 1);
+ mVectorState.mBasicAnimator = ObjectAnimator.ofFloat(this, "AnimationFraction", 0, 0);
if (theme != null && canApplyTheme()) {
applyTheme(theme);
@@ -213,7 +213,7 @@ public class VectorDrawable extends Drawable {
@Override
public void jumpToCurrentState() {
- mVectorState.mBasicAnimator.end();
+ stop();
}
/**
@@ -318,7 +318,7 @@ public class VectorDrawable extends Drawable {
private void animateBackward() {
if (!mVectorState.mBasicAnimator.isStarted()) {
- mVectorState.mBasicAnimator.setFloatValues(.99f, 0);
+ mVectorState.mBasicAnimator.setFloatValues(1, 0);
start();
}
}
@@ -985,7 +985,13 @@ public class VectorDrawable extends Drawable {
for (int j = 0; j < sp.length; j++) {
mSeqMap.add(sp[j].trim());
- VectorDrawable.VPath path = groups.get(j).get(sp[j]);
+
+ final VectorDrawable.VPath path = groups.get(j).get(sp[j]);
+ if (path == null) {
+ throw new XmlPullParserException(a.getPositionDescription()
+ + " missing path with name: " + sp[j]);
+ }
+
path.mAnimated = true;
paths[j] = path;
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 4ed73c3..5ce7ba6 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -91,7 +91,8 @@ public:
void destroy();
bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
- void makeCurrent(EGLSurface surface);
+ // Returns true if the current surface changed, false if it was already current
+ bool makeCurrent(EGLSurface surface);
void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
void swapBuffers(EGLSurface surface);
@@ -250,8 +251,8 @@ void GlobalContext::destroy() {
mCurrentSurface = EGL_NO_SURFACE;
}
-void GlobalContext::makeCurrent(EGLSurface surface) {
- if (isCurrent(surface)) return;
+bool GlobalContext::makeCurrent(EGLSurface surface) {
+ if (isCurrent(surface)) return false;
if (surface == EGL_NO_SURFACE) {
// If we are setting EGL_NO_SURFACE we don't care about any of the potential
@@ -263,6 +264,7 @@ void GlobalContext::makeCurrent(EGLSurface surface) {
(void*)surface, egl_error_str());
}
mCurrentSurface = surface;
+ return true;
}
void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
@@ -281,7 +283,6 @@ void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height
void GlobalContext::swapBuffers(EGLSurface surface) {
eglSwapBuffers(mEglDisplay, surface);
EGLint err = eglGetError();
- // TODO: Check whether we need to special case EGL_CONTEXT_LOST
LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
"Encountered EGL error %d %s during rendering", err, egl_error_str(err));
}
@@ -344,8 +345,8 @@ void CanvasContext::setSurface(EGLNativeWindowType window) {
if (mEglSurface != EGL_NO_SURFACE) {
mDirtyRegionsEnabled = mGlobalContext->enableDirtyRegions(mEglSurface);
- mGlobalContext->makeCurrent(mEglSurface);
mHaveNewSurface = true;
+ makeCurrent();
}
}
@@ -357,7 +358,7 @@ void CanvasContext::swapBuffers() {
void CanvasContext::requireSurface() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"requireSurface() called but no surface set!");
- mGlobalContext->makeCurrent(mEglSurface);
+ makeCurrent();
}
bool CanvasContext::initialize(EGLNativeWindowType window) {
@@ -383,7 +384,9 @@ void CanvasContext::setup(int width, int height) {
}
void CanvasContext::makeCurrent() {
- mGlobalContext->makeCurrent(mEglSurface);
+ // TODO: Figure out why this workaround is needed, see b/13913604
+ // In the meantime this matches the behavior of GLRenderer, so it is not a regression
+ mHaveNewSurface |= mGlobalContext->makeCurrent(mEglSurface);
}
void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters,
@@ -475,7 +478,7 @@ Layer* CanvasContext::createTextureLayer() {
void CanvasContext::requireGlContext() {
if (mEglSurface != EGL_NO_SURFACE) {
- mGlobalContext->makeCurrent(mEglSurface);
+ makeCurrent();
} else {
mGlobalContext->usePBufferSurface();
}
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 1283e9b..2616b6c 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -217,7 +217,7 @@ public class Ringtone {
if (mAudioManager.getStreamVolume(mStreamType) != 0) {
mLocalPlayer.start();
}
- } else if (mAllowRemote) {
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
final Uri canonicalUri = mUri.getCanonicalUri();
try {
mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType);
@@ -239,7 +239,7 @@ public class Ringtone {
public void stop() {
if (mLocalPlayer != null) {
destroyLocalPlayer();
- } else if (mAllowRemote) {
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
try {
mRemotePlayer.stop(mRemoteToken);
} catch (RemoteException e) {
@@ -264,7 +264,7 @@ public class Ringtone {
public boolean isPlaying() {
if (mLocalPlayer != null) {
return mLocalPlayer.isPlaying();
- } else if (mAllowRemote) {
+ } else if (mAllowRemote && (mRemotePlayer != null)) {
try {
return mRemotePlayer.isPlaying(mRemoteToken);
} catch (RemoteException e) {
diff --git a/media/java/android/media/routeprovider/IRouteConnection.aidl b/media/java/android/media/routeprovider/IRouteConnection.aidl
new file mode 100644
index 0000000..15c8039
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteConnection.aidl
@@ -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.
+ */
+
+package android.media.routeprovider;
+
+import android.media.session.RouteCommand;
+import android.os.ResultReceiver;
+
+/**
+ * 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/IRouteProvider.aidl b/media/java/android/media/routeprovider/IRouteProvider.aidl
new file mode 100644
index 0000000..c36f6a7
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProvider.aidl
@@ -0,0 +1,36 @@
+/* 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
new file mode 100644
index 0000000..9185347
--- /dev/null
+++ b/media/java/android/media/routeprovider/IRouteProviderCallback.aidl
@@ -0,0 +1,32 @@
+/* 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
new file mode 100644
index 0000000..9214ff8
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteConnection.java
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+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
new file mode 100644
index 0000000..9693dc6
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteInterfaceHandler.java
@@ -0,0 +1,245 @@
+/*
+ * 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.Session;
+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 Session} 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.
+ */
+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 Session} 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 Session
+ */
+ 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
new file mode 100644
index 0000000..dcef79a
--- /dev/null
+++ b/media/java/android/media/routeprovider/RoutePlaybackControlsHandler.java
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+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
new file mode 100644
index 0000000..6ebfb5b
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteProviderService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.routeprovider;
+
+import android.app.Service;
+import android.content.Intent;
+import android.media.routeprovider.IRouteProvider;
+import android.media.routeprovider.IRouteProviderCallback;
+import android.media.session.RouteEvent;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for defining a route provider service.
+ * <p>
+ * A route provider offers media routes which represent destinations to which
+ * applications may connect, control, and send content. This provides a means
+ * for Android applications to interact with a variety of media streaming
+ * devices such as speakers or television sets.
+ * <p>
+ * The system will bind to your provider when an active app is interested in
+ * routes that may be discovered through your provider. After binding, the
+ * system will send updates on which routes to discover through
+ * {@link #updateDiscoveryRequests(List)}. The system will call
+ * {@link #getMatchingRoutes(List)} with a subset of filters when a route is
+ * needed for a specific app.
+ * <p>
+ * TODO add documentation for how the sytem knows an app is interested. Maybe
+ * interface declarations in the manifest.
+ * <p>
+ * The system will only start a provider when an app may discover routes through
+ * it. If your service needs to run at other times you are responsible for
+ * managing its lifecycle.
+ * <p>
+ * Declare your route provider service in your application manifest like this:
+ * <p>
+ *
+ * <pre>
+ * &lt;service android:name=".MyRouteProviderService"
+ * android:label="@string/my_route_provider_service">
+ * &lt;intent-filter>
+ * &lt;action android:name="com.android.media.session.MediaRouteProvider" />
+ * &lt;/intent-filter>
+ * &lt;/service>
+ * </pre>
+ */
+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
new file mode 100644
index 0000000..7bc5722
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteRequest.aidl
@@ -0,0 +1,18 @@
+/* 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
new file mode 100644
index 0000000..9913566
--- /dev/null
+++ b/media/java/android/media/routeprovider/RouteRequest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.SessionInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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.
+ */
+public final class RouteRequest implements Parcelable {
+ private final SessionInfo mSessionInfo;
+ private final RouteOptions mOptions;
+ private final boolean mActive;
+
+ /**
+ * @hide
+ */
+ public RouteRequest(SessionInfo info, RouteOptions connRequest,
+ boolean active) {
+ mSessionInfo = info;
+ mOptions = connRequest;
+ mActive = active;
+ }
+
+ private RouteRequest(Parcel in) {
+ mSessionInfo = SessionInfo.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 SessionInfo 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 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/session/IMediaSession.aidl b/media/java/android/media/session/ISession.aidl
index aed7641..ca77f04 100644
--- a/media/java/android/media/session/IMediaSession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -15,25 +15,33 @@
package android.media.session;
-import android.media.session.IMediaController;
+import android.media.session.ISessionController;
import android.media.session.MediaMetadata;
+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;
/**
* Interface to a MediaSession in the system.
* @hide
*/
-interface IMediaSession {
+interface ISession {
void sendEvent(String event, in Bundle data);
- IMediaController getMediaController();
+ ISessionController getController();
void setTransportPerformerEnabled();
- void setRouteState(in Bundle routeState);
- void setRoute(in Bundle mediaRouteDescriptor);
- List<String> getSupportedInterfaces();
void publish();
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 sendRouteCommand(in RouteCommand event, in ResultReceiver cb);
+
// These commands are for the TransportPerformer
void setMetadata(in MediaMetadata metadata);
void setPlaybackState(in PlaybackState state);
diff --git a/media/java/android/media/session/IMediaSessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 7c183e0..f04cbcc 100644
--- a/media/java/android/media/session/IMediaSessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -16,6 +16,9 @@
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;
@@ -23,10 +26,13 @@ import android.os.ResultReceiver;
/**
* @hide
*/
-oneway interface IMediaSessionCallback {
+oneway interface ISessionCallback {
void onCommand(String command, in Bundle extras, in ResultReceiver cb);
- void onMediaButton(in Intent mediaRequestIntent);
- void onRequestRouteChange(in Bundle route);
+ void onMediaButton(in Intent mediaButtonIntent);
+ void onRequestRouteChange(in RouteInfo route);
+ void onRouteConnected(in RouteInfo route, in RouteOptions options);
+ void onRouteStateChange(int state);
+ void onRouteEvent(in RouteEvent event);
// These callbacks are for the TransportPerformer
void onPlay();
diff --git a/media/java/android/media/session/IMediaController.aidl b/media/java/android/media/session/ISessionController.aidl
index d34e973..e2e046f 100644
--- a/media/java/android/media/session/IMediaController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -17,7 +17,7 @@ package android.media.session;
import android.content.Intent;
import android.media.Rating;
-import android.media.session.IMediaControllerCallback;
+import android.media.session.ISessionControllerCallback;
import android.media.session.MediaMetadata;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -28,12 +28,13 @@ import android.view.KeyEvent;
* Interface to a MediaSession in the system.
* @hide
*/
-interface IMediaController {
+interface ISessionController {
void sendCommand(String command, in Bundle extras, in ResultReceiver cb);
void sendMediaButton(in KeyEvent mediaButton);
- void registerCallbackListener(in IMediaControllerCallback cb);
- void unregisterCallbackListener(in IMediaControllerCallback cb);
+ void registerCallbackListener(in ISessionControllerCallback cb);
+ void unregisterCallbackListener(in ISessionControllerCallback cb);
boolean isTransportControlEnabled();
+ void showRoutePicker();
// These commands are for the TransportController
void play();
diff --git a/media/java/android/media/session/IMediaControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index 3651f1b..bc1ae05 100644
--- a/media/java/android/media/session/IMediaControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -16,15 +16,16 @@
package android.media.session;
import android.media.session.MediaMetadata;
+import android.media.session.RouteInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
/**
* @hide
*/
-oneway interface IMediaControllerCallback {
+oneway interface ISessionControllerCallback {
void onEvent(String event, in Bundle extras);
- void onRouteChanged(in Bundle route);
+ void onRouteChanged(in RouteInfo route);
// These callbacks are for the TransportController
void onPlaybackStateChanged(in PlaybackState state);
diff --git a/media/java/android/media/session/IMediaSessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 0b4328e..84b9a0f 100644
--- a/media/java/android/media/session/IMediaSessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -15,14 +15,14 @@
package android.media.session;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
import android.os.Bundle;
/**
* Interface to the MediaSessionManagerService
* @hide
*/
-interface IMediaSessionManager {
- IMediaSession createSession(String packageName, in IMediaSessionCallback cb, String tag);
+interface ISessionManager {
+ ISession createSession(String packageName, in ISessionCallback cb, String tag);
} \ No newline at end of file
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index b3506b3..14d9fb1 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,12 +15,11 @@
*/
package android.media.session;
-import android.media.RemoteControlClient;
import android.os.Parcel;
import android.os.Parcelable;
/**
- * Playback state for a {@link MediaSession}. This includes a state like
+ * Playback state for a {@link Session}. This includes a state like
* {@link PlaybackState#PLAYSTATE_PLAYING}, the current playback position,
* and the current control capabilities.
*/
@@ -147,6 +146,14 @@ public final class PlaybackState implements Parcelable {
*/
public final static int PLAYSTATE_ERROR = 7;
+ /**
+ * 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 #PLAYSTATE_NONE}. If
+ * the connection failed {@link #PLAYSTATE_ERROR} should be used.
+ */
+ public final static int PLAYSTATE_CONNECTING = 8;
+
private int mState;
private long mPosition;
private long mBufferPosition;
diff --git a/media/java/android/media/session/Route.java b/media/java/android/media/session/Route.java
new file mode 100644
index 0000000..c9530a6
--- /dev/null
+++ b/media/java/android/media/session/Route.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+public final class Route {
+ private static final String TAG = "Route";
+ private final RouteInfo mInfo;
+ private final Session mSession;
+ private final RouteOptions mOptions;
+
+ /**
+ * @hide
+ */
+ public Route(RouteInfo info, RouteOptions options, Session 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
+ */
+ Session getSession() {
+ return mSession;
+ }
+}
diff --git a/media/java/android/media/session/MediaSessionToken.aidl b/media/java/android/media/session/RouteCommand.aidl
index 5812682..725b308 100644
--- a/media/java/android/media/session/MediaSessionToken.aidl
+++ b/media/java/android/media/session/RouteCommand.aidl
@@ -15,4 +15,4 @@
package android.media.session;
-parcelable MediaSessionToken;
+parcelable RouteCommand;
diff --git a/media/java/android/media/session/RouteCommand.java b/media/java/android/media/session/RouteCommand.java
new file mode 100644
index 0000000..358bc0a
--- /dev/null
+++ b/media/java/android/media/session/RouteCommand.java
@@ -0,0 +1,117 @@
+/*
+ * 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
new file mode 100644
index 0000000..6966207
--- /dev/null
+++ b/media/java/android/media/session/RouteEvent.aidl
@@ -0,0 +1,18 @@
+/* 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
new file mode 100644
index 0000000..918e410
--- /dev/null
+++ b/media/java/android/media/session/RouteEvent.java
@@ -0,0 +1,120 @@
+/*
+ * 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
new file mode 100644
index 0000000..c5f50c8
--- /dev/null
+++ b/media/java/android/media/session/RouteInfo.aidl
@@ -0,0 +1,18 @@
+/* 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
new file mode 100644
index 0000000..17df969
--- /dev/null
+++ b/media/java/android/media/session/RouteInfo.java
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ */
+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
index 2391f27..e9c9fd3 100644
--- a/media/java/android/media/session/RouteInterface.java
+++ b/media/java/android/media/session/RouteInterface.java
@@ -17,135 +17,160 @@ package android.media.session;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcelable;
import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.ArrayList;
/**
- * Routes can support multiple interfaces for MediaSessions to interact with. To
- * add a standard interface you should implement that interface's RouteInterface
- * Stub and register it with the session. The set of supported commands is
- * dependent on the specific interface's implementation.
- * <p>
- * A MediaInterface can be registered by calling TODO. Once added an interface
- * will be used by Sessions to decide how they communicate with a session and
- * cannot be removed, so all interfaces that you plan to support should be added
- * when the route is created.
+ * A route can support multiple interfaces for a {@link Session} 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 RouteTransportControls
+ * @see RoutePlaybackControls for an example
*/
public final class RouteInterface {
- private static final String TAG = "MediaInterface";
+ private static final String TAG = "RouteInterface";
- private static final String KEY_RESULT = "result";
+ /**
+ * 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 MediaController mController;
+ private final Route mRoute;
private final String mIface;
+ private final Session mSession;
+
+ private final Object mLock = new Object();
+ private final ArrayList<EventHandler> mListeners = new ArrayList<EventHandler>();
/**
* @hide
*/
- RouteInterface(MediaController controller, String iface) {
- mController = controller;
+ RouteInterface(Route route, String iface, Session session) {
+ mRoute = route;
mIface = iface;
+ mSession = session;
+ mSession.addInterfaceListener(iface, mEventListener);
}
- public void sendCommand(String command, Bundle params, ResultReceiver cb) {
- // TODO
+ /**
+ * 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) {
- // TODO See MediaController for add/remove pattern
+ 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) {
- // TODO
+ if (listener == null) {
+ throw new IllegalArgumentException("listener may not be null");
+ }
+ synchronized (mLock) {
+ int index = findIndexOfListenerLocked(listener);
+ if (index != -1) {
+ mListeners.remove(index);
+ }
+ }
}
- // TODO decide on list of supported types
- private static Bundle writeResultToBundle(Object v) {
- Bundle b = new Bundle();
- if (v == null) {
- // Don't send anything if null
- } else if (v instanceof String) {
- b.putString(KEY_RESULT, (String) v);
- } else if (v instanceof Integer) {
- b.putInt(KEY_RESULT, (Integer) v);
- } else if (v instanceof Bundle) {
- // Must be before Parcelable
- b.putBundle(KEY_RESULT, (Bundle) v);
- } else if (v instanceof Parcelable) {
- b.putParcelable(KEY_RESULT, (Parcelable) v);
- } else if (v instanceof Short) {
- b.putShort(KEY_RESULT, (Short) v);
- } else if (v instanceof Long) {
- b.putLong(KEY_RESULT, (Long) v);
- } else if (v instanceof Float) {
- b.putFloat(KEY_RESULT, (Float) v);
- } else if (v instanceof Double) {
- b.putDouble(KEY_RESULT, (Double) v);
- } else if (v instanceof Boolean) {
- b.putBoolean(KEY_RESULT, (Boolean) v);
- } else if (v instanceof CharSequence) {
- // Must be after String
- b.putCharSequence(KEY_RESULT, (CharSequence) v);
- } else if (v instanceof boolean[]) {
- b.putBooleanArray(KEY_RESULT, (boolean[]) v);
- } else if (v instanceof byte[]) {
- b.putByteArray(KEY_RESULT, (byte[]) v);
- } else if (v instanceof String[]) {
- b.putStringArray(KEY_RESULT, (String[]) v);
- } else if (v instanceof CharSequence[]) {
- // Must be after String[] and before Object[]
- b.putCharSequenceArray(KEY_RESULT, (CharSequence[]) v);
- } else if (v instanceof IBinder) {
- b.putBinder(KEY_RESULT, (IBinder) v);
- } else if (v instanceof Parcelable[]) {
- b.putParcelableArray(KEY_RESULT, (Parcelable[]) v);
- } else if (v instanceof int[]) {
- b.putIntArray(KEY_RESULT, (int[]) v);
- } else if (v instanceof long[]) {
- b.putLongArray(KEY_RESULT, (long[]) v);
- } else if (v instanceof Byte) {
- b.putByte(KEY_RESULT, (Byte) v);
+ 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 b;
+ return -1;
}
- public abstract static class Stub {
-
- /**
- * The name of an interface should be a fully qualified name to prevent
- * namespace collisions. Example: "com.myproject.MyPlaybackInterface"
- *
- * @return The name of this interface
- */
- public abstract String getName();
-
- /**
- * This is called when a command is received that matches the interface
- * you registered. Commands can come from any app with a MediaController
- * reference to the session.
- *
- * @see MediaController
- * @see MediaSession
- * @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.
- */
- public abstract void onCommand(String command, Bundle args, ResultReceiver cb);
-
- public final void sendEvent(MediaSession session, String event, Bundle extras) {
- // TODO
+ 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
@@ -166,9 +191,9 @@ public final class RouteInterface {
private static final class EventHandler extends Handler {
- private final RouteInterface.EventListener mListener;
+ private final EventListener mListener;
- public EventHandler(Looper looper, RouteInterface.EventListener cb) {
+ public EventHandler(Looper looper, EventListener cb) {
super(looper, null, true);
mListener = cb;
}
diff --git a/media/java/android/media/session/RouteOptions.aidl b/media/java/android/media/session/RouteOptions.aidl
new file mode 100644
index 0000000..feaf517
--- /dev/null
+++ b/media/java/android/media/session/RouteOptions.aidl
@@ -0,0 +1,18 @@
+/* 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
new file mode 100644
index 0000000..5105867
--- /dev/null
+++ b/media/java/android/media/session/RouteOptions.java
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+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
new file mode 100644
index 0000000..a3ffb58
--- /dev/null
+++ b/media/java/android/media/session/RoutePlaybackControls.java
@@ -0,0 +1,161 @@
+/*
+ * 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.ResultReceiver;
+
+/**
+ * A standard media control interface for Routes that support queueing and
+ * transport controls. Routes may support multiple interfaces for MediaSessions
+ * to interact with.
+ */
+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/media/java/android/media/session/RouteTransportControls.java b/media/java/android/media/session/RouteTransportControls.java
deleted file mode 100644
index 665fd10..0000000
--- a/media/java/android/media/session/RouteTransportControls.java
+++ /dev/null
@@ -1,230 +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.RemoteControlClient;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * A standard media control interface for Routes. Routes can support multiple
- * interfaces for MediaSessions to interact with. TODO rewrite for routes
- */
-public final class RouteTransportControls {
- private static final String TAG = "RouteTransportControls";
- public static final String NAME = "android.media.session.RouteTransportControls";
-
- private static final String KEY_VALUE1 = "value1";
-
- private static final String METHOD_FAST_FORWARD = "fastForward";
- private static final String METHOD_GET_CURRENT_POSITION = "getCurrentPosition";
- private static final String METHOD_GET_CAPABILITIES = "getCapabilities";
-
- private static final String EVENT_PLAYSTATE_CHANGE = "playstateChange";
- private static final String EVENT_METADATA_CHANGE = "metadataChange";
-
- private final MediaController mController;
- private final RouteInterface mIface;
-
- private RouteTransportControls(RouteInterface iface, MediaController controller) {
- mIface = iface;
- mController = controller;
- }
-
- public static RouteTransportControls from(MediaController controller) {
-// MediaInterface iface = controller.getInterface(NAME);
-// if (iface != null) {
-// return new RouteTransportControls(iface, controller);
-// }
- return null;
- }
-
- /**
- * Send a play command to the route. TODO rename resume() and use messaging
- * protocol, not KeyEvent
- */
- public void play() {
- // TODO
- }
-
- /**
- * Send a pause command to the session.
- */
- public void pause() {
- // TODO
- }
-
- /**
- * Set the rate at which to fastforward. Valid values are in the range [0,1]
- * with actual rates depending on the implementation.
- *
- * @param rate
- */
- public void fastForward(float rate) {
- if (rate < 0 || rate > 1) {
- throw new IllegalArgumentException("Rate must be between 0 and 1 inclusive");
- }
- Bundle b = new Bundle();
- b.putFloat(KEY_VALUE1, rate);
- mIface.sendCommand(METHOD_FAST_FORWARD, b, null);
- }
-
- public void getCurrentPosition(ResultReceiver cb) {
- mIface.sendCommand(METHOD_GET_CURRENT_POSITION, null, cb);
- }
-
- public void getCapabilities(ResultReceiver cb) {
- mIface.sendCommand(METHOD_GET_CAPABILITIES, null, cb);
- }
-
- public void addListener(Listener listener) {
- mIface.addListener(listener.mListener);
- }
-
- public void addListener(Listener listener, Handler handler) {
- mIface.addListener(listener.mListener, handler);
- }
-
- public void removeListener(Listener listener) {
- mIface.removeListener(listener.mListener);
- }
-
- public static abstract class Stub extends RouteInterface.Stub {
- private final MediaSession mSession;
-
- public Stub(MediaSession session) {
- mSession = session;
- }
-
- @Override
- public String getName() {
- return NAME;
- }
-
- @Override
- public void onCommand(String method, Bundle extras, ResultReceiver cb) {
- if (TextUtils.isEmpty(method)) {
- return;
- }
- Bundle result;
- if (METHOD_FAST_FORWARD.equals(method)) {
- fastForward(extras.getFloat(KEY_VALUE1, -1));
- } else if (METHOD_GET_CURRENT_POSITION.equals(method)) {
- if (cb != null) {
- result = new Bundle();
- result.putLong(KEY_VALUE1, getCurrentPosition());
- cb.send(0, result);
- }
- } else if (METHOD_GET_CAPABILITIES.equals(method)) {
- if (cb != null) {
- result = new Bundle();
- result.putLong(KEY_VALUE1, getCapabilities());
- cb.send(0, result);
- }
- }
- }
-
- /**
- * Override to handle fast forwarding. Valid values are [0,1] inclusive.
- * The interpretation of the rate is up to the implementation. If no
- * rate was included with the command a rate of -1 will be used by
- * default.
- *
- * @param rate The rate at which to fast forward as a multiplier
- */
- public void fastForward(float rate) {
- Log.w(TAG, "fastForward is not supported.");
- }
-
- /**
- * 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;
- }
-
- /**
- * Publish the current playback state to the system and any controllers.
- * Valid values are defined in {@link RemoteControlClient}. TODO move
- * play states somewhere else.
- *
- * @param state
- */
- public final void updatePlaybackState(int state) {
- Bundle extras = new Bundle();
- extras.putInt(KEY_VALUE1, state);
- sendEvent(mSession, EVENT_PLAYSTATE_CHANGE, extras);
- }
- }
-
- /**
- * Register this event listener using TODO to receive
- * TransportControlInterface events from a session.
- *
- * @see RouteInterface.EventListener
- */
- public static abstract class Listener {
-
- private RouteInterface.EventListener mListener = new RouteInterface.EventListener() {
- @Override
- public final void onEvent(String event, Bundle args) {
- if (EVENT_PLAYSTATE_CHANGE.equals(event)) {
- onPlaybackStateChange(args.getInt(KEY_VALUE1));
- } else if (EVENT_METADATA_CHANGE.equals(event)) {
- onMetadataUpdate(args);
- }
- }
- };
-
- /**
- * 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(Bundle metadata) {
- }
- }
-
-}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/Session.java
index 23c3035..8ccd788 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/Session.java
@@ -18,9 +18,9 @@ package android.media.session;
import android.content.Intent;
import android.media.Rating;
-import android.media.session.IMediaController;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.session.ISessionController;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -33,6 +33,7 @@ import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
/**
* Allows interaction with media controllers, media routes, volume keys, media
@@ -44,11 +45,11 @@ import java.util.ArrayList;
* media to multiple routes or to provide finer grain controls of media.
* <p>
* A MediaSession is created by calling
- * {@link MediaSessionManager#createSession(String)}. Once a session is created
+ * {@link SessionManager#createSession(String)}. Once a session is created
* apps that have the MEDIA_CONTENT_CONTROL permission can interact with the
- * session through {@link MediaSessionManager#getActiveSessions()}. The owner of
+ * session through {@link SessionManager#getActiveSessions()}. The owner of
* the session may also use {@link #getSessionToken()} to allow apps without
- * this permission to create a {@link MediaController} to interact with this
+ * this permission to create a {@link SessionController} to interact with this
* session.
* <p>
* To receive commands, media keys, and other events a Callback must be set with
@@ -59,12 +60,13 @@ import java.util.ArrayList;
* <p>
* MediaSession objects are thread safe
*/
-public final class MediaSession {
- private static final String TAG = "MediaSession";
+public final class Session {
+ private static final String TAG = "Session";
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 String KEY_COMMAND = "command";
private static final String KEY_EXTRAS = "extras";
@@ -72,32 +74,33 @@ public final class MediaSession {
private final Object mLock = new Object();
- private final MediaSessionToken mSessionToken;
- private final IMediaSession mBinder;
+ private final SessionToken mSessionToken;
+ private final ISession mBinder;
private final CallbackStub mCbStub;
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
// TODO route interfaces
- private final ArrayMap<String, RouteInterface.Stub> mInterfaces
- = new ArrayMap<String, RouteInterface.Stub>();
+ private final ArrayMap<String, RouteInterface.EventListener> mInterfaceListeners
+ = new ArrayMap<String, RouteInterface.EventListener>();
private TransportPerformer mPerformer;
+ private Route mRoute;
private boolean mPublished = false;;
/**
* @hide
*/
- public MediaSession(IMediaSession binder, CallbackStub cbStub) {
+ public Session(ISession binder, CallbackStub cbStub) {
mBinder = binder;
mCbStub = cbStub;
- IMediaController controllerBinder = null;
+ ISessionController controllerBinder = null;
try {
- controllerBinder = mBinder.getMediaController();
+ controllerBinder = mBinder.getController();
} catch (RemoteException e) {
throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
}
- mSessionToken = new MediaSessionToken(controllerBinder);
+ mSessionToken = new SessionToken(controllerBinder);
}
/**
@@ -109,6 +112,13 @@ public final class MediaSession {
addCallback(callback, null);
}
+ /**
+ * Add a callback to receive updates for the MediaSession. This includes
+ * events like route updates, media buttons, and focus changes.
+ *
+ * @param callback The callback to receive updates on.
+ * @param handler The handler that events should be posted on.
+ */
public void addCallback(Callback callback, Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("Callback cannot be null");
@@ -126,6 +136,11 @@ public final class MediaSession {
}
}
+ /**
+ * Remove a callback. It will no longer receive updates.
+ *
+ * @param callback The callback to remove.
+ */
public void removeCallback(Callback callback) {
synchronized (mLock) {
removeCallbackLocked(callback);
@@ -186,30 +201,6 @@ public final class MediaSession {
}
/**
- * Add an interface that can be used by MediaSessions. TODO make this a
- * route provider api
- *
- * @see RouteInterface
- * @param iface The interface to add
- * @hide
- */
- public void addInterface(RouteInterface.Stub iface) {
- if (iface == null) {
- throw new IllegalArgumentException("Stub cannot be null");
- }
- String name = iface.getName();
- if (TextUtils.isEmpty(name)) {
- throw new IllegalArgumentException("Stub must return a valid name");
- }
- if (mInterfaces.containsKey(iface)) {
- throw new IllegalArgumentException("Interface is already added");
- }
- synchronized (mLock) {
- mInterfaces.put(iface.getName(), iface);
- }
- }
-
- /**
* Send a proprietary event to all MediaControllers listening to this
* Session. It's up to the Controller/Session owner to determine the meaning
* of any events.
@@ -243,16 +234,92 @@ public final class MediaSession {
/**
* Retrieve a token object that can be used by apps to create a
- * {@link MediaController} for interacting with this session. The owner of
+ * {@link SessionController} for interacting with this session. The owner of
* the session is responsible for deciding how to distribute these tokens.
*
* @return A token that can be used to create a MediaController for this
* session
*/
- public MediaSessionToken getSessionToken() {
+ public SessionToken getSessionToken() {
return mSessionToken;
}
+ /**
+ * 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.
+ */
+ 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.
+ *
+ * @param route The route to disconnect from.
+ */
+ public void disconnect(RouteInfo route) {
+ // TODO
+ }
+
+ /**
+ * 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.
+ */
+ 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;
+ }
+
private MessageHandler getHandlerForCallbackLocked(Callback cb) {
if (cb == null) {
throw new IllegalArgumentException("Callback cannot be null");
@@ -297,10 +364,19 @@ public final class MediaSession {
}
}
- private void postRequestRouteChange(Bundle mediaRouteDescriptor) {
+ private void postRequestRouteChange(RouteInfo route) {
+ synchronized (mLock) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).post(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(MSG_ROUTE_CHANGE, mediaRouteDescriptor);
+ mCallbacks.get(i).post(MSG_ROUTE_CONNECTED, mRoute);
}
}
}
@@ -346,26 +422,49 @@ public final class MediaSession {
* The app is responsible for connecting to the new route and migrating
* ongoing playback if necessary.
*
- * @param descriptor
+ * @param route
*/
- public void onRequestRouteChange(Bundle descriptor) {
+ 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
+ */
+ 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>
+ * </ul>
+ *
+ * @param route The route that disconnected
+ * @param reason The reason for the disconnect
+ */
+ public void onRouteDisconnected(Route route, int reason) {
}
}
/**
* @hide
*/
- public static class CallbackStub extends IMediaSessionCallback.Stub {
- private WeakReference<MediaSession> mMediaSession;
+ public static class CallbackStub extends ISessionCallback.Stub {
+ private WeakReference<Session> mMediaSession;
- public void setMediaSession(MediaSession session) {
- mMediaSession = new WeakReference<MediaSession>(session);
+ public void setMediaSession(Session session) {
+ mMediaSession = new WeakReference<Session>(session);
}
@Override
public void onCommand(String command, Bundle extras, ResultReceiver cb)
throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
session.postCommand(command, extras, cb);
}
@@ -373,23 +472,31 @@ public final class MediaSession {
@Override
public void onMediaButton(Intent mediaButtonIntent) throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
session.postMediaButton(mediaButtonIntent);
}
}
@Override
- public void onRequestRouteChange(Bundle mediaRouteDescriptor) throws RemoteException {
- MediaSession session = mMediaSession.get();
+ public void onRequestRouteChange(RouteInfo route) throws RemoteException {
+ Session session = mMediaSession.get();
if (session != null) {
- session.postRequestRouteChange(mediaRouteDescriptor);
+ session.postRequestRouteChange(route);
+ }
+ }
+
+ @Override
+ public void onRouteConnected(RouteInfo route, RouteOptions options) {
+ Session session = mMediaSession.get();
+ if (session != null) {
+ session.postRouteConnected(route, options);
}
}
@Override
public void onPlay() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -400,7 +507,7 @@ public final class MediaSession {
@Override
public void onPause() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -411,7 +518,7 @@ public final class MediaSession {
@Override
public void onStop() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -422,7 +529,7 @@ public final class MediaSession {
@Override
public void onNext() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -433,7 +540,7 @@ public final class MediaSession {
@Override
public void onPrevious() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -444,7 +551,7 @@ public final class MediaSession {
@Override
public void onFastForward() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -455,7 +562,7 @@ public final class MediaSession {
@Override
public void onRewind() throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -466,7 +573,7 @@ public final class MediaSession {
@Override
public void onSeekTo(long pos) throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -477,7 +584,7 @@ public final class MediaSession {
@Override
public void onRate(Rating rating) throws RemoteException {
- MediaSession session = mMediaSession.get();
+ Session session = mMediaSession.get();
if (session != null) {
TransportPerformer tp = session.getTransportPerformer();
if (tp != null) {
@@ -486,12 +593,32 @@ public final class MediaSession {
}
}
+ @Override
+ public void onRouteEvent(RouteEvent event) throws RemoteException {
+ Session 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
+
+ }
+
}
private class MessageHandler extends Handler {
- private MediaSession.Callback mCallback;
+ private Session.Callback mCallback;
- public MessageHandler(Looper looper, MediaSession.Callback callback) {
+ public MessageHandler(Looper looper, Session.Callback callback) {
super(looper, null, true);
mCallback = callback;
}
@@ -511,11 +638,13 @@ public final class MediaSession {
mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
break;
case MSG_ROUTE_CHANGE:
- mCallback.onRequestRouteChange((Bundle) msg.obj);
+ mCallback.onRequestRouteChange((RouteInfo) msg.obj);
+ break;
+ case MSG_ROUTE_CONNECTED:
+ mCallback.onRouteConnected((Route) msg.obj);
break;
}
}
- msg.recycle();
}
public void post(int what, Object obj) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/SessionController.java
index afd8b11..dc4f7d9 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/SessionController.java
@@ -34,21 +34,21 @@ import java.util.ArrayList;
* other commands can be sent to the session. A callback may be registered to
* receive updates from the session, such as metadata and play state changes.
* <p>
- * A MediaController can be created through {@link MediaSessionManager} if you
+ * A MediaController can be created through {@link SessionManager} if you
* hold the "android.permission.MEDIA_CONTENT_CONTROL" permission or directly if
- * you have a {@link MediaSessionToken} from the session owner.
+ * you have a {@link SessionToken} from the session owner.
* <p>
* MediaController objects are thread-safe.
*/
-public final class MediaController {
- private static final String TAG = "MediaController";
+public final class SessionController {
+ private static final String TAG = "SessionController";
private static final int MSG_EVENT = 1;
private static final int MESSAGE_PLAYBACK_STATE = 2;
private static final int MESSAGE_METADATA = 3;
private static final int MSG_ROUTE = 4;
- private final IMediaController mSessionBinder;
+ private final ISessionController mSessionBinder;
private final CallbackStub mCbStub = new CallbackStub(this);
private final ArrayList<MessageHandler> mCallbacks = new ArrayList<MessageHandler>();
@@ -58,15 +58,15 @@ public final class MediaController {
private TransportController mTransportController;
- private MediaController(IMediaController sessionBinder) {
+ private SessionController(ISessionController sessionBinder) {
mSessionBinder = sessionBinder;
}
/**
* @hide
*/
- public static MediaController fromBinder(IMediaController sessionBinder) {
- MediaController controller = new MediaController(sessionBinder);
+ public static SessionController fromBinder(ISessionController sessionBinder) {
+ SessionController controller = new SessionController(sessionBinder);
try {
controller.mSessionBinder.registerCallbackListener(controller.mCbStub);
if (controller.mSessionBinder.isTransportControlEnabled()) {
@@ -87,7 +87,7 @@ public final class MediaController {
* @param token The session token to use
* @return A controller for the session or null
*/
- public static MediaController fromToken(MediaSessionToken token) {
+ public static SessionController fromToken(SessionToken token) {
return fromBinder(token.getBinder());
}
@@ -181,10 +181,22 @@ public final class MediaController {
}
}
+ /**
+ * Request that the route picker be shown for this session. This should
+ * generally be called in response to a user action.
+ */
+ public void showRoutePicker() {
+ try {
+ mSessionBinder.showRoutePicker();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Dead object in showRoutePicker", e);
+ }
+ }
+
/*
* @hide
*/
- IMediaController getSessionBinder() {
+ ISessionController getSessionBinder() {
return mSessionBinder;
}
@@ -247,10 +259,10 @@ public final class MediaController {
}
}
- private void postRouteChanged(Bundle routeDescriptor) {
+ private void postRouteChanged(RouteInfo route) {
synchronized (mLock) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- mCallbacks.get(i).post(MSG_ROUTE, null, routeDescriptor);
+ mCallbacks.get(i).post(MSG_ROUTE, route, null);
}
}
}
@@ -275,36 +287,36 @@ public final class MediaController {
*
* @param route
*/
- public void onRouteChanged(Bundle route) {
+ public void onRouteChanged(RouteInfo route) {
}
}
- private final static class CallbackStub extends IMediaControllerCallback.Stub {
- private final WeakReference<MediaController> mController;
+ private final static class CallbackStub extends ISessionControllerCallback.Stub {
+ private final WeakReference<SessionController> mController;
- public CallbackStub(MediaController controller) {
- mController = new WeakReference<MediaController>(controller);
+ public CallbackStub(SessionController controller) {
+ mController = new WeakReference<SessionController>(controller);
}
@Override
public void onEvent(String event, Bundle extras) {
- MediaController controller = mController.get();
+ SessionController controller = mController.get();
if (controller != null) {
controller.postEvent(event, extras);
}
}
@Override
- public void onRouteChanged(Bundle mediaRouteDescriptor) {
- MediaController controller = mController.get();
+ public void onRouteChanged(RouteInfo route) {
+ SessionController controller = mController.get();
if (controller != null) {
- controller.postRouteChanged(mediaRouteDescriptor);
+ controller.postRouteChanged(route);
}
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
- MediaController controller = mController.get();
+ SessionController controller = mController.get();
if (controller != null) {
TransportController tc = controller.getTransportController();
if (tc != null) {
@@ -315,7 +327,7 @@ public final class MediaController {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
- MediaController controller = mController.get();
+ SessionController controller = mController.get();
if (controller != null) {
TransportController tc = controller.getTransportController();
if (tc != null) {
@@ -327,9 +339,9 @@ public final class MediaController {
}
private final static class MessageHandler extends Handler {
- private final MediaController.Callback mCallback;
+ private final SessionController.Callback mCallback;
- public MessageHandler(Looper looper, MediaController.Callback cb) {
+ public MessageHandler(Looper looper, SessionController.Callback cb) {
super(looper, null, true);
mCallback = cb;
}
@@ -341,7 +353,7 @@ public final class MediaController {
mCallback.onEvent((String) msg.obj, msg.getData());
break;
case MSG_ROUTE:
- mCallback.onRouteChanged(msg.getData());
+ mCallback.onRouteChanged((RouteInfo) msg.obj);
}
}
diff --git a/media/java/android/media/session/SessionInfo.java b/media/java/android/media/session/SessionInfo.java
new file mode 100644
index 0000000..22d8ab1
--- /dev/null
+++ b/media/java/android/media/session/SessionInfo.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+/**
+ * Information about a media session, including the owner's package name.
+ */
+public final class SessionInfo implements Parcelable {
+ private final String mId;
+ private final String mPackageName;
+
+ /**
+ * @hide
+ */
+ public SessionInfo(String id, String packageName) {
+ mId = id;
+ mPackageName = packageName;
+ }
+
+ private SessionInfo(Parcel in) {
+ mId = in.readString();
+ mPackageName = in.readString();
+ }
+
+ /**
+ * Get the package name of the owner of this session.
+ *
+ * @return The owner's package name
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Get the unique id for this session.
+ *
+ * @return The id for the session.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mPackageName);
+ }
+
+ public static final Parcelable.Creator<SessionInfo> CREATOR
+ = new Parcelable.Creator<SessionInfo>() {
+ @Override
+ public SessionInfo createFromParcel(Parcel in) {
+ return new SessionInfo(in);
+ }
+
+ @Override
+ public SessionInfo[] newArray(int size) {
+ return new SessionInfo[size];
+ }
+ };
+}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/SessionManager.java
index e3f2d9c..15bf0e3 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/SessionManager.java
@@ -17,7 +17,7 @@
package android.media.session;
import android.content.Context;
-import android.media.session.IMediaSessionManager;
+import android.media.session.ISessionManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -35,37 +35,37 @@ import java.util.List;
* get an instance of this class.
* <p>
*
- * @see MediaSession
- * @see MediaController
+ * @see Session
+ * @see SessionController
*/
-public final class MediaSessionManager {
- private static final String TAG = "MediaSessionManager";
+public final class SessionManager {
+ private static final String TAG = "SessionManager";
- private final IMediaSessionManager mService;
+ private final ISessionManager mService;
private Context mContext;
/**
* @hide
*/
- public MediaSessionManager(Context context) {
+ public SessionManager(Context context) {
// Consider rewriting like DisplayManagerGlobal
// Decide if we need context
mContext = context;
IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
- mService = IMediaSessionManager.Stub.asInterface(b);
+ mService = ISessionManager.Stub.asInterface(b);
}
/**
* Creates a new session.
*
* @param tag A short name for debugging purposes
- * @return a {@link MediaSession} for the new session
+ * @return a {@link Session} for the new session
*/
- public MediaSession createSession(String tag) {
+ public Session createSession(String tag) {
try {
- MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
- MediaSession session = new MediaSession(mService
+ Session.CallbackStub cbStub = new Session.CallbackStub();
+ Session session = new Session(mService
.createSession(mContext.getPackageName(), cbStub, tag), cbStub);
cbStub.setMediaSession(session);
@@ -83,8 +83,8 @@ public final class MediaSessionManager {
*
* @return a list of controllers for ongoing sessions
*/
- public List<MediaController> getActiveSessions() {
+ public List<SessionController> getActiveSessions() {
// TODO
- return new ArrayList<MediaController>();
+ return new ArrayList<SessionController>();
}
}
diff --git a/media/java/android/media/session/SessionToken.aidl b/media/java/android/media/session/SessionToken.aidl
new file mode 100644
index 0000000..db35f85
--- /dev/null
+++ b/media/java/android/media/session/SessionToken.aidl
@@ -0,0 +1,18 @@
+/* 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 SessionToken;
diff --git a/media/java/android/media/session/MediaSessionToken.java b/media/java/android/media/session/SessionToken.java
index dbb4964..59486f6 100644
--- a/media/java/android/media/session/MediaSessionToken.java
+++ b/media/java/android/media/session/SessionToken.java
@@ -16,28 +16,28 @@
package android.media.session;
-import android.media.session.IMediaController;
+import android.media.session.ISessionController;
import android.os.Parcel;
import android.os.Parcelable;
-public class MediaSessionToken implements Parcelable {
- private IMediaController mBinder;
+public class SessionToken implements Parcelable {
+ private ISessionController mBinder;
/**
* @hide
*/
- MediaSessionToken(IMediaController binder) {
+ SessionToken(ISessionController binder) {
mBinder = binder;
}
- private MediaSessionToken(Parcel in) {
- mBinder = IMediaController.Stub.asInterface(in.readStrongBinder());
+ private SessionToken(Parcel in) {
+ mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
}
/**
* @hide
*/
- IMediaController getBinder() {
+ ISessionController getBinder() {
return mBinder;
}
@@ -51,16 +51,16 @@ public class MediaSessionToken implements Parcelable {
dest.writeStrongBinder(mBinder.asBinder());
}
- public static final Parcelable.Creator<MediaSessionToken> CREATOR
- = new Parcelable.Creator<MediaSessionToken>() {
+ public static final Parcelable.Creator<SessionToken> CREATOR
+ = new Parcelable.Creator<SessionToken>() {
@Override
- public MediaSessionToken createFromParcel(Parcel in) {
- return new MediaSessionToken(in);
+ public SessionToken createFromParcel(Parcel in) {
+ return new SessionToken(in);
}
@Override
- public MediaSessionToken[] newArray(int size) {
- return new MediaSessionToken[size];
+ public SessionToken[] newArray(int size) {
+ return new SessionToken[size];
}
};
}
diff --git a/media/java/android/media/session/TransportController.java b/media/java/android/media/session/TransportController.java
index 15b11f3..9574df6 100644
--- a/media/java/android/media/session/TransportController.java
+++ b/media/java/android/media/session/TransportController.java
@@ -34,12 +34,12 @@ public final class TransportController {
private final Object mLock = new Object();
private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
- private final IMediaController mBinder;
+ private final ISessionController mBinder;
/**
* @hide
*/
- public TransportController(IMediaController binder) {
+ public TransportController(ISessionController binder) {
mBinder = binder;
}
diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java
index b96db20..eddffd1 100644
--- a/media/java/android/media/session/TransportPerformer.java
+++ b/media/java/android/media/session/TransportPerformer.java
@@ -34,12 +34,12 @@ public final class TransportPerformer {
private final Object mLock = new Object();
private final ArrayList<MessageHandler> mListeners = new ArrayList<MessageHandler>();
- private IMediaSession mBinder;
+ private ISession mBinder;
/**
* @hide
*/
- public TransportPerformer(IMediaSession binder) {
+ public TransportPerformer(ISession binder) {
mBinder = binder;
}
diff --git a/packages/DefaultContainerService/Android.mk b/packages/DefaultContainerService/Android.mk
index 9961168..0de2c1f 100644
--- a/packages/DefaultContainerService/Android.mk
+++ b/packages/DefaultContainerService/Android.mk
@@ -7,7 +7,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := DefaultContainerService
-LOCAL_REQUIRED_MODULES := libdefcontainer_jni
+LOCAL_JNI_SHARED_LIBRARIES := libdefcontainer_jni
LOCAL_CERTIFICATE := platform
diff --git a/packages/Keyguard/res/layout/keyguard_bouncer.xml b/packages/Keyguard/res/layout/keyguard_bouncer.xml
index dedf427..8716ebc 100644
--- a/packages/Keyguard/res/layout/keyguard_bouncer.xml
+++ b/packages/Keyguard/res/layout/keyguard_bouncer.xml
@@ -24,8 +24,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <include layout="@layout/keyguard_simple_host_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ <include
+ style="@style/BouncerSecurityContainer"
+ layout="@layout/keyguard_simple_host_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
</FrameLayout>
diff --git a/packages/Keyguard/res/layout/keyguard_sim_pin_view.xml b/packages/Keyguard/res/layout/keyguard_sim_pin_view.xml
index e96220e..0e2b33a 100644
--- a/packages/Keyguard/res/layout/keyguard_sim_pin_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_sim_pin_view.xml
@@ -52,7 +52,7 @@
android:orientation="horizontal"
android:layout_weight="1"
>
- <TextView android:id="@+id/pinEntry"
+ <TextView android:id="@+id/simPinEntry"
android:editable="true"
android:layout_width="0dip"
android:layout_height="match_parent"
@@ -96,7 +96,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="1"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -105,7 +105,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="2"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -114,7 +114,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="3"
/>
</LinearLayout>
@@ -130,7 +130,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="4"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -139,7 +139,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="5"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -148,7 +148,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="6"
/>
</LinearLayout>
@@ -164,7 +164,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="7"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -173,7 +173,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="8"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -182,7 +182,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="9"
/>
</LinearLayout>
@@ -203,7 +203,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/simPinEntry"
androidprv:digit="0"
/>
<ImageButton
diff --git a/packages/Keyguard/res/layout/keyguard_sim_puk_view.xml b/packages/Keyguard/res/layout/keyguard_sim_puk_view.xml
index bf15ba0..88049a7 100644
--- a/packages/Keyguard/res/layout/keyguard_sim_puk_view.xml
+++ b/packages/Keyguard/res/layout/keyguard_sim_puk_view.xml
@@ -53,7 +53,7 @@
android:orientation="horizontal"
android:layout_weight="1"
>
- <TextView android:id="@+id/pinEntry"
+ <TextView android:id="@+id/pukEntry"
android:editable="true"
android:layout_width="0dip"
android:layout_height="match_parent"
@@ -97,7 +97,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="1"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -106,7 +106,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="2"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -115,7 +115,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="3"
/>
</LinearLayout>
@@ -131,7 +131,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="4"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -140,7 +140,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="5"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -149,7 +149,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="6"
/>
</LinearLayout>
@@ -165,7 +165,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="7"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -174,7 +174,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="8"
/>
<view class="com.android.keyguard.NumPadKey"
@@ -183,7 +183,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="9"
/>
</LinearLayout>
@@ -204,7 +204,7 @@
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
- androidprv:textView="@+id/pinEntry"
+ androidprv:textView="@+id/pukEntry"
androidprv:digit="0"
/>
<ImageButton
diff --git a/packages/Keyguard/res/values-sw600dp/styles.xml b/packages/Keyguard/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..e632e76
--- /dev/null
+++ b/packages/Keyguard/res/values-sw600dp/styles.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <style name="BouncerSecurityContainer">
+ <item name="android:layout_gravity">center</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/Keyguard/res/values/styles.xml b/packages/Keyguard/res/values/styles.xml
index 4a034aa..b54ac50 100644
--- a/packages/Keyguard/res/values/styles.xml
+++ b/packages/Keyguard/res/values/styles.xml
@@ -81,4 +81,9 @@
<item name="android:mirrorForRtl">true</item>
</style>
+ <style name="BouncerSecurityContainer">
+ <item name="android:layout_marginBottom">32dp</item>
+ <item name="android:layout_gravity">center_horizontal|bottom</item>
+ </style>
+
</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
index d6a4f52..4791956 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java
@@ -87,7 +87,7 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView
@Override
protected int getPasswordTextViewId() {
- return R.id.pinEntry;
+ return R.id.simPinEntry;
}
@Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
index 04cbde1..b9c7f51 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java
@@ -138,7 +138,7 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView
@Override
protected int getPasswordTextViewId() {
- return R.id.pinEntry;
+ return R.id.pukEntry;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 460f122..41b5b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -56,7 +56,6 @@ public class StatusBarKeyguardViewManager {
private boolean mScreenOn = false;
private KeyguardBouncer mBouncer;
private boolean mShowing;
- private boolean mOccluded = false;
public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils) {
@@ -103,10 +102,8 @@ public class StatusBarKeyguardViewManager {
}
public void showBouncer() {
- if (!mOccluded) {
- mBouncer.show();
- updateBackButtonState();
- }
+ mBouncer.show();
+ updateBackButtonState();
}
/**
@@ -155,13 +152,6 @@ public class StatusBarKeyguardViewManager {
}
public void setOccluded(boolean occluded) {
- mOccluded = occluded;
- if (occluded) {
- mPhoneStatusBar.hideKeyguard();
- mBouncer.hide();
- } else {
- showBouncerOrKeyguard();
- }
mStatusBarWindowManager.setKeyguardOccluded(occluded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 716e326..d175d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -99,10 +99,14 @@ public class StatusBarWindowManager {
}
private void adjustScreenOrientation(State state) {
- if (!state.isKeyguardShowingAndNotOccluded() || mKeyguardScreenRotation) {
- mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ if (state.isKeyguardShowingAndNotOccluded()) {
+ if (mKeyguardScreenRotation) {
+ mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ } else {
+ mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ }
} else {
- mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
}
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index d9566d5..3c4e951 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -25,7 +25,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PacProcessor
LOCAL_CERTIFICATE := platform
-LOCAL_REQUIRED_MODULES := libjni_pacprocessor
+LOCAL_JNI_SHARED_LIBRARIES := libjni_pacprocessor
include $(BUILD_PACKAGE)
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 2cf94d0..0550dd4 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -3580,6 +3580,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
SwipeDismissLayout layout, float progress, float translate) {
WindowManager.LayoutParams newParams = getAttributes();
newParams.x = (int) translate;
+ newParams.alpha = 1 - progress;
setAttributes(newParams);
int flags = 0;
@@ -3595,6 +3596,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
public void onSwipeCancelled(SwipeDismissLayout layout) {
WindowManager.LayoutParams newParams = getAttributes();
newParams.x = 0;
+ newParams.alpha = 1;
setAttributes(newParams);
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
}
diff --git a/rs/java/android/renderscript/ScriptIntrinsicResize.java b/rs/java/android/renderscript/ScriptIntrinsicResize.java
new file mode 100644
index 0000000..fe56699
--- /dev/null
+++ b/rs/java/android/renderscript/ScriptIntrinsicResize.java
@@ -0,0 +1,112 @@
+/*
+ * 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.renderscript;
+
+/**
+ * Intrinsic for performing a resize of a 2D allocation.
+ */
+public final class ScriptIntrinsicResize extends ScriptIntrinsic {
+ private Allocation mInput;
+
+ private ScriptIntrinsicResize(long id, RenderScript rs) {
+ super(id, rs);
+ }
+
+ /**
+ * Supported elements types are {@link Element#U8}, {@link
+ * Element#U8_2}, {@link Element#U8_3}, {@link Element#U8_4}
+ *
+ * @param rs The RenderScript context
+ *
+ * @return ScriptIntrinsicResize
+ */
+ public static ScriptIntrinsicResize create(RenderScript rs) {
+ long id = rs.nScriptIntrinsicCreate(12, 0);
+ ScriptIntrinsicResize si = new ScriptIntrinsicResize(id, rs);
+ return si;
+
+ }
+
+ /**
+ * Set the input of the resize.
+ * Must match the element type supplied during create.
+ *
+ * @param ain The input allocation.
+ */
+ public void setInput(Allocation ain) {
+ Element e = ain.getElement();
+ if (!e.isCompatible(Element.U8(mRS)) &&
+ !e.isCompatible(Element.U8_2(mRS)) &&
+ !e.isCompatible(Element.U8_3(mRS)) &&
+ !e.isCompatible(Element.U8_4(mRS))) {
+ throw new RSIllegalArgumentException("Unsuported element type.");
+ }
+
+ mInput = ain;
+ setVar(0, ain);
+ }
+
+ /**
+ * Get a FieldID for the input field of this intrinsic.
+ *
+ * @return Script.FieldID The FieldID object.
+ */
+ public Script.FieldID getFieldID_Input() {
+ return createFieldID(0, null);
+ }
+
+
+ /**
+ * Resize copy the input allocation to the output specified. The
+ * Allocation is rescaled if necessary using bi-cubic
+ * interpolation.
+ *
+ * @param aout Output allocation. Element type must match
+ * current input. Must not be same as input.
+ */
+ public void forEach_bicubic(Allocation aout) {
+ if (aout == mInput) {
+ throw new RSIllegalArgumentException("Output cannot be same as Input.");
+ }
+ forEach_bicubic(aout, null);
+ }
+
+ /**
+ * Resize copy the input allocation to the output specified. The
+ * Allocation is rescaled if necessary using bi-cubic
+ * interpolation.
+ *
+ * @param aout Output allocation. Element type must match
+ * current input.
+ * @param opt LaunchOptions for clipping
+ */
+ public void forEach_bicubic(Allocation aout, Script.LaunchOptions opt) {
+ forEach(0, null, aout, null, opt);
+ }
+
+ /**
+ * Get a KernelID for this intrinsic kernel.
+ *
+ * @return Script.KernelID The KernelID object.
+ */
+ public Script.KernelID getKernelID_bicubic() {
+ return createKernelID(0, 2, null, null);
+ }
+
+
+}
+
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 671b43d..18a2e31 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -47,24 +47,29 @@
using namespace android;
-#define PER_ARRAY_TYPE(flag, fnc, ...) { \
+#define PER_ARRAY_TYPE(flag, fnc, readonly, ...) { \
jint len = 0; \
void *ptr = NULL; \
size_t typeBytes = 0; \
+ jint relFlag = 0; \
+ if (readonly) { \
+ /* The on-release mode should only be JNI_ABORT for read-only accesses. */ \
+ relFlag = JNI_ABORT; \
+ } \
switch(dataType) { \
case RS_TYPE_FLOAT_32: \
len = _env->GetArrayLength((jfloatArray)data); \
ptr = _env->GetFloatArrayElements((jfloatArray)data, flag); \
typeBytes = 4; \
fnc(__VA_ARGS__); \
- _env->ReleaseFloatArrayElements((jfloatArray)data, (jfloat *)ptr, JNI_ABORT); \
+ _env->ReleaseFloatArrayElements((jfloatArray)data, (jfloat *)ptr, relFlag); \
return; \
case RS_TYPE_FLOAT_64: \
len = _env->GetArrayLength((jdoubleArray)data); \
ptr = _env->GetDoubleArrayElements((jdoubleArray)data, flag); \
typeBytes = 8; \
fnc(__VA_ARGS__); \
- _env->ReleaseDoubleArrayElements((jdoubleArray)data, (jdouble *)ptr, JNI_ABORT);\
+ _env->ReleaseDoubleArrayElements((jdoubleArray)data, (jdouble *)ptr, relFlag); \
return; \
case RS_TYPE_SIGNED_8: \
case RS_TYPE_UNSIGNED_8: \
@@ -72,7 +77,7 @@ using namespace android;
ptr = _env->GetByteArrayElements((jbyteArray)data, flag); \
typeBytes = 1; \
fnc(__VA_ARGS__); \
- _env->ReleaseByteArrayElements((jbyteArray)data, (jbyte*)ptr, JNI_ABORT); \
+ _env->ReleaseByteArrayElements((jbyteArray)data, (jbyte*)ptr, relFlag); \
return; \
case RS_TYPE_SIGNED_16: \
case RS_TYPE_UNSIGNED_16: \
@@ -80,7 +85,7 @@ using namespace android;
ptr = _env->GetShortArrayElements((jshortArray)data, flag); \
typeBytes = 2; \
fnc(__VA_ARGS__); \
- _env->ReleaseShortArrayElements((jshortArray)data, (jshort *)ptr, JNI_ABORT); \
+ _env->ReleaseShortArrayElements((jshortArray)data, (jshort *)ptr, relFlag); \
return; \
case RS_TYPE_SIGNED_32: \
case RS_TYPE_UNSIGNED_32: \
@@ -88,7 +93,7 @@ using namespace android;
ptr = _env->GetIntArrayElements((jintArray)data, flag); \
typeBytes = 4; \
fnc(__VA_ARGS__); \
- _env->ReleaseIntArrayElements((jintArray)data, (jint *)ptr, JNI_ABORT); \
+ _env->ReleaseIntArrayElements((jintArray)data, (jint *)ptr, relFlag); \
return; \
case RS_TYPE_SIGNED_64: \
case RS_TYPE_UNSIGNED_64: \
@@ -96,7 +101,7 @@ using namespace android;
ptr = _env->GetLongArrayElements((jlongArray)data, flag); \
typeBytes = 8; \
fnc(__VA_ARGS__); \
- _env->ReleaseLongArrayElements((jlongArray)data, (jlong *)ptr, JNI_ABORT); \
+ _env->ReleaseLongArrayElements((jlongArray)data, (jlong *)ptr, relFlag); \
return; \
default: \
break; \
@@ -672,6 +677,7 @@ static void ReleaseBitmapCallback(void *bmp)
}
+// Copies from the Java object data into the Allocation pointed to by _alloc.
static void
nAllocationData1D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint offset, jint lod,
jint count, jobject data, jint sizeBytes, jint dataType)
@@ -679,9 +685,10 @@ nAllocationData1D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint off
RsAllocation *alloc = (RsAllocation *)_alloc;
LOG_API("nAllocation1DData, con(%p), adapter(%p), offset(%i), count(%i), sizeBytes(%i), dataType(%i)",
(RsContext)con, (RsAllocation)alloc, offset, count, sizeBytes, dataType);
- PER_ARRAY_TYPE(NULL, rsAllocation1DData, (RsContext)con, alloc, offset, lod, count, ptr, sizeBytes);
+ PER_ARRAY_TYPE(NULL, rsAllocation1DData, true, (RsContext)con, alloc, offset, lod, count, ptr, sizeBytes);
}
+// Copies from the Java array data into the Allocation pointed to by alloc.
static void
// native void rsnAllocationElementData1D(long con, long id, int xoff, int compIdx, byte[] d, int sizeBytes);
nAllocationElementData1D(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jint offset, jint lod, jint compIdx, jbyteArray data, jint sizeBytes)
@@ -693,6 +700,7 @@ nAllocationElementData1D(JNIEnv *_env, jobject _this, jlong con, jlong alloc, ji
_env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
}
+// Copies from the Java object data into the Allocation pointed to by _alloc.
static void
nAllocationData2D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xoff, jint yoff, jint lod, jint _face,
jint w, jint h, jobject data, jint sizeBytes, jint dataType)
@@ -701,9 +709,11 @@ nAllocationData2D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xof
RsAllocationCubemapFace face = (RsAllocationCubemapFace)_face;
LOG_API("nAllocation2DData, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i) type(%i)",
(RsContext)con, alloc, xoff, yoff, w, h, sizeBytes, dataType);
- PER_ARRAY_TYPE(NULL, rsAllocation2DData, (RsContext)con, alloc, xoff, yoff, lod, face, w, h, ptr, sizeBytes, 0);
+ PER_ARRAY_TYPE(NULL, rsAllocation2DData, true, (RsContext)con, alloc, xoff, yoff, lod, face, w, h, ptr, sizeBytes, 0);
}
+// Copies from the Allocation pointed to by srcAlloc into the Allocation
+// pointed to by dstAlloc.
static void
nAllocationData2D_alloc(JNIEnv *_env, jobject _this, jlong con,
jlong dstAlloc, jint dstXoff, jint dstYoff,
@@ -728,6 +738,7 @@ nAllocationData2D_alloc(JNIEnv *_env, jobject _this, jlong con,
srcMip, srcFace);
}
+// Copies from the Java object data into the Allocation pointed to by _alloc.
static void
nAllocationData3D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xoff, jint yoff, jint zoff, jint lod,
jint w, jint h, jint d, jobject data, int sizeBytes, int dataType)
@@ -735,9 +746,11 @@ nAllocationData3D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xof
RsAllocation *alloc = (RsAllocation *)_alloc;
LOG_API("nAllocation3DData, con(%p), alloc(%p), xoff(%i), yoff(%i), zoff(%i), lod(%i), w(%i), h(%i), d(%i), sizeBytes(%i)",
(RsContext)con, (RsAllocation)alloc, xoff, yoff, zoff, lod, w, h, d, sizeBytes);
- PER_ARRAY_TYPE(NULL, rsAllocation3DData, (RsContext)con, alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
+ PER_ARRAY_TYPE(NULL, rsAllocation3DData, true, (RsContext)con, alloc, xoff, yoff, zoff, lod, w, h, d, ptr, sizeBytes, 0);
}
+// Copies from the Allocation pointed to by srcAlloc into the Allocation
+// pointed to by dstAlloc.
static void
nAllocationData3D_alloc(JNIEnv *_env, jobject _this, jlong con,
jlong dstAlloc, jint dstXoff, jint dstYoff, jint dstZoff,
@@ -761,14 +774,16 @@ nAllocationData3D_alloc(JNIEnv *_env, jobject _this, jlong con,
}
+// Copies from the Allocation pointed to by _alloc into the Java object data.
static void
nAllocationRead(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jobject data, int dataType)
{
RsAllocation *alloc = (RsAllocation *)_alloc;
LOG_API("nAllocationRead, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc);
- PER_ARRAY_TYPE(0, rsAllocationRead, (RsContext)con, alloc, ptr, len * typeBytes);
+ PER_ARRAY_TYPE(0, rsAllocationRead, false, (RsContext)con, alloc, ptr, len * typeBytes);
}
+// Copies from the Allocation pointed to by _alloc into the Java object data.
static void
nAllocationRead1D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint offset, jint lod,
jint count, jobject data, int sizeBytes, int dataType)
@@ -776,9 +791,10 @@ nAllocationRead1D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint off
RsAllocation *alloc = (RsAllocation *)_alloc;
LOG_API("nAllocation1DRead, con(%p), adapter(%p), offset(%i), count(%i), sizeBytes(%i), dataType(%i)",
(RsContext)con, alloc, offset, count, sizeBytes, dataType);
- PER_ARRAY_TYPE(0, rsAllocation1DRead, (RsContext)con, alloc, offset, lod, count, ptr, sizeBytes);
+ PER_ARRAY_TYPE(0, rsAllocation1DRead, false, (RsContext)con, alloc, offset, lod, count, ptr, sizeBytes);
}
+// Copies from the Allocation pointed to by _alloc into the Java object data.
static void
nAllocationRead2D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xoff, jint yoff, jint lod, jint _face,
jint w, jint h, jobject data, int sizeBytes, int dataType)
@@ -787,7 +803,7 @@ nAllocationRead2D(JNIEnv *_env, jobject _this, jlong con, jlong _alloc, jint xof
RsAllocationCubemapFace face = (RsAllocationCubemapFace)_face;
LOG_API("nAllocation2DRead, con(%p), adapter(%p), xoff(%i), yoff(%i), w(%i), h(%i), len(%i) type(%i)",
(RsContext)con, alloc, xoff, yoff, w, h, sizeBytes, dataType);
- PER_ARRAY_TYPE(0, rsAllocation2DRead, (RsContext)con, alloc, xoff, yoff, lod, face, w, h, ptr, sizeBytes, 0);
+ PER_ARRAY_TYPE(0, rsAllocation2DRead, false, (RsContext)con, alloc, xoff, yoff, lod, face, w, h, ptr, sizeBytes, 0);
}
static jlong
@@ -1023,7 +1039,7 @@ nScriptGetVarV(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
jint len = _env->GetArrayLength(data);
jbyte *ptr = _env->GetByteArrayElements(data, NULL);
rsScriptGetVarV((RsContext)con, (RsScript)script, slot, ptr, len);
- _env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
+ _env->ReleaseByteArrayElements(data, ptr, 0);
}
static void
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 071417b..6697b60 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -517,6 +517,16 @@ public final class DisplayManagerService extends SystemService {
return -1;
}
+ private void setVirtualDisplaySurfaceInternal(IBinder appToken, Surface surface) {
+ synchronized (mSyncRoot) {
+ if (mVirtualDisplayAdapter == null) {
+ return;
+ }
+
+ mVirtualDisplayAdapter.setVirtualDisplaySurfaceLocked(appToken, surface);
+ }
+ }
+
private void releaseVirtualDisplayInternal(IBinder appToken) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
@@ -1221,9 +1231,6 @@ public final class DisplayManagerService extends SystemService {
throw new IllegalArgumentException("width, height, and densityDpi must be "
+ "greater than 0");
}
- if (surface == null) {
- throw new IllegalArgumentException("surface must not be null");
- }
if (callingUid != Process.SYSTEM_UID &&
(flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
@@ -1255,6 +1262,16 @@ public final class DisplayManagerService extends SystemService {
}
@Override // Binder call
+ public void setVirtualDisplaySurface(IBinder appToken, Surface surface) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setVirtualDisplaySurfaceInternal(appToken, surface);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void releaseVirtualDisplay(IBinder appToken) {
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 95ca0d2..a165f26 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -69,6 +69,13 @@ final class VirtualDisplayAdapter extends DisplayAdapter {
return device;
}
+ public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
+ VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
+ if (device != null) {
+ device.setSurfaceLocked(surface);
+ }
+ }
+
public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
if (device != null) {
@@ -144,6 +151,17 @@ final class VirtualDisplayAdapter extends DisplayAdapter {
}
}
+ public void setSurfaceLocked(Surface surface) {
+ if (mSurface != surface) {
+ if ((mSurface != null) != (surface != null)) {
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ }
+ sendTraversalRequestLocked();
+ mSurface = surface;
+ mInfo = null;
+ }
+ }
+
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
@@ -171,6 +189,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter {
}
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+ mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF;
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
}
diff --git a/services/core/java/com/android/server/media/MediaRouteProviderProxy.java b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
new file mode 100644
index 0000000..d314ea7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderProxy.java
@@ -0,0 +1,379 @@
+/*
+ * 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.Session;
+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.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;
+
+ private Intent mBindIntent;
+ // Interfaces declared in the manifest
+ private ArrayList<String> mInterfaces;
+ private ArrayList<RouteConnectionRecord> mConnections = new ArrayList<RouteConnectionRecord>();
+ private Handler mHandler = new Handler();
+
+ private IRouteProvider mBinder;
+ private boolean mRunning;
+ 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;
+ mInterfaces = interfaces;
+ mBindIntent = new Intent(RouteProviderService.SERVICE_INTERFACE);
+ mBindIntent.setComponent(mComponentName);
+ }
+
+ /**
+ * 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);
+ 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;
+ }
+
+ private void updateBinding() {
+ if (shouldBind()) {
+ bind();
+ } else {
+ unbind();
+ }
+ }
+
+ private boolean shouldBind() {
+ return mRunning && mInterested;
+ }
+
+ 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
new file mode 100644
index 0000000..cf1d95a
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouteProviderWatcher.java
@@ -0,0 +1,229 @@
+/*
+ * 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);
+ }
+ }
+
+ public void stop() {
+ if (mRunning) {
+ mRunning = false;
+
+ mContext.unregisterReceiver(mScanPackagesReceiver);
+ mHandler.removeCallbacks(mScanPackagesRunnable);
+
+ // Stop all providers.
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).stop();
+ }
+ }
+ }
+
+ 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 1ff925c..ac7f4f3 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,11 +17,20 @@
package com.android.server.media;
import android.content.Intent;
-import android.media.session.IMediaController;
-import android.media.session.IMediaControllerCallback;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISessionController;
+import android.media.session.ISessionControllerCallback;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.SessionController;
import android.media.session.MediaMetadata;
+import android.media.session.RouteCommand;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.RouteEvent;
+import android.media.session.Session;
+import android.media.session.SessionInfo;
+import android.media.session.RouteInterface;
import android.media.session.PlaybackState;
import android.media.Rating;
import android.os.Bundle;
@@ -31,37 +40,44 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.KeyEvent;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
/**
* This is the system implementation of a Session. Apps will interact with the
* MediaSession wrapper class instead.
*/
public class MediaSessionRecord implements IBinder.DeathRecipient {
- private static final String TAG = "MediaSessionImpl";
+ private static final String TAG = "MediaSessionRecord";
private final MessageHandler mHandler;
private final int mPid;
- private final String mPackageName;
+ private final SessionInfo mSessionInfo;
private final String mTag;
private final ControllerStub mController;
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
- private final Object mControllerLock = new Object();
- private final ArrayList<IMediaControllerCallback> mControllerCallbacks =
- new ArrayList<IMediaControllerCallback>();
- private final ArrayList<String> mInterfaces = new ArrayList<String>();
+ private final Object mLock = new Object();
+ private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
+ new ArrayList<ISessionControllerCallback>();
+ private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
private boolean mTransportPerformerEnabled = false;
- private Bundle mRoute;
+ private RouteInfo mRoute;
+ private RouteOptions mRequest;
+ private RouteConnectionRecord mConnection;
+ // TODO define a RouteState class with relevant info
+ private int mRouteState;
// TransportPerformer fields
@@ -72,10 +88,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private boolean mIsPublished = false;
- public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
+ public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag,
MediaSessionService service, Handler handler) {
mPid = pid;
- mPackageName = packageName;
+ mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName);
mTag = tag;
mController = new ControllerStub();
mSession = new SessionStub();
@@ -84,31 +100,140 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mHandler = new MessageHandler(handler.getLooper());
}
- public IMediaSession getSessionBinder() {
+ /**
+ * Get the binder for the {@link Session}.
+ *
+ * @return The session binder apps talk to.
+ */
+ public ISession getSessionBinder() {
return mSession;
}
- public IMediaController getControllerBinder() {
+ /**
+ * Get the binder for the {@link SessionController}.
+ *
+ * @return The controller binder apps talk to.
+ */
+ public ISessionController getControllerBinder() {
return mController;
}
- @Override
- public void binderDied() {
- mService.sessionDied(this);
+ /**
+ * 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.
+ */
+ public SessionInfo getSessionInfo() {
+ return mSessionInfo;
+ }
+
+ /**
+ * 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) {
+ if (mConnection != null) {
+ mConnection.disconnect();
+ mConnection = null;
+ }
+ }
+ 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);
+ }
+
+ /**
+ * 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 (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
+ Log.w(TAG, "setRouteConnected: connected route is stale");
+ // TODO figure out disconnection path
+ return false;
+ }
+ if (request != mRequest) {
+ Log.w(TAG, "setRouteConnected: connection request is stale");
+ // TODO figure out disconnection path
+ return false;
+ }
+ mConnection = connection;
+ mConnection.setListener(mConnectionListener);
+ mSessionCb.sendRouteConnected();
+ }
+ return true;
+ }
+
+ /**
+ * Check if this session has been published by the app yet.
+ *
+ * @return True if it has been published, false otherwise.
+ */
public boolean isPublished() {
return mIsPublished;
}
+ @Override
+ public void binderDied() {
+ mService.sessionDied(this);
+ }
+
private void onDestroy() {
mService.destroySession(this);
}
private void pushPlaybackStateUpdate() {
- synchronized (mControllerLock) {
+ synchronized (mLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mControllerCallbacks.get(i);
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onPlaybackStateChanged(mPlaybackState);
} catch (RemoteException e) {
@@ -120,9 +245,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
private void pushMetadataUpdate() {
- synchronized (mControllerLock) {
+ synchronized (mLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mControllerCallbacks.get(i);
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onMetadataChanged(mMetadata);
} catch (RemoteException e) {
@@ -134,9 +259,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
private void pushRouteUpdate() {
- synchronized (mControllerLock) {
+ synchronized (mLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mControllerCallbacks.get(i);
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onRouteChanged(mRoute);
} catch (RemoteException e) {
@@ -148,44 +273,63 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
private void pushEvent(String event, Bundle data) {
- synchronized (mControllerLock) {
+ synchronized (mLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mControllerCallbacks.get(i);
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onEvent(event, data);
} catch (RemoteException e) {
- Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
- mControllerCallbacks.remove(i);
+ Log.w(TAG, "Error with callback in pushEvent.", e);
}
}
}
}
- private final class SessionStub extends IMediaSession.Stub {
+ private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
+ synchronized (mLock) {
+ 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 final RouteConnectionRecord.Listener mConnectionListener
+ = new RouteConnectionRecord.Listener() {
@Override
- public void destroy() {
- onDestroy();
+ public void onEvent(RouteEvent event) {
+ RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
+ event.getEvent(), event.getExtras());
+ mSessionCb.sendRouteEvent(eventForSession);
}
@Override
- public void sendEvent(String event, Bundle data) {
- mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
+ public void disconnect() {
+ // TODO
}
+ };
+ private final class SessionStub extends ISession.Stub {
@Override
- public IMediaController getMediaController() {
- return mController;
+ public void destroy() {
+ onDestroy();
}
@Override
- public void setRouteState(Bundle routeState) {
+ public void sendEvent(String event, Bundle data) {
+ mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
}
@Override
- public void setRoute(Bundle mediaRouteDescriptor) {
- mRoute = mediaRouteDescriptor;
- mHandler.post(MessageHandler.MSG_UPDATE_ROUTE);
+ public ISessionController getController() {
+ return mController;
}
@Override
@@ -198,11 +342,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public List<String> getSupportedInterfaces() {
- return mInterfaces;
- }
-
- @Override
public void setMetadata(MediaMetadata metadata) {
mMetadata = metadata;
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
@@ -218,12 +357,44 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
public void setRatingType(int type) {
mRatingType = type;
}
+
+ @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 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);
+ }
+ }
}
class SessionCb {
- private final IMediaSessionCallback mCb;
+ private final ISessionCallback mCb;
- public SessionCb(IMediaSessionCallback cb) {
+ public SessionCb(ISessionCallback cb) {
mCb = cb;
}
@@ -245,6 +416,38 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
+ public void sendRouteChange(RouteInfo route) {
+ try {
+ mCb.onRequestRouteChange(route);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteChange.", e);
+ }
+ }
+
+ public void sendRouteStateChange(int state) {
+ try {
+ mCb.onRouteStateChange(state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+ }
+ }
+
+ public void sendRouteEvent(RouteEvent event) {
+ try {
+ mCb.onRouteEvent(event);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
+ }
+ }
+
+ public void sendRouteConnected() {
+ try {
+ mCb.onRouteConnected(mRoute, mRequest);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
+ }
+ }
+
public void play() {
try {
mCb.onPlay();
@@ -318,7 +521,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
- class ControllerStub extends IMediaController.Stub {
+ class ControllerStub extends ISessionController.Stub {
@Override
public void sendCommand(String command, Bundle extras, ResultReceiver cb)
throws RemoteException {
@@ -331,8 +534,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public void registerCallbackListener(IMediaControllerCallback cb) {
- synchronized (mControllerLock) {
+ public void registerCallbackListener(ISessionControllerCallback cb) {
+ synchronized (mLock) {
if (!mControllerCallbacks.contains(cb)) {
mControllerCallbacks.add(cb);
}
@@ -340,9 +543,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public void unregisterCallbackListener(IMediaControllerCallback cb)
+ public void unregisterCallbackListener(ISessionControllerCallback cb)
throws RemoteException {
- synchronized (mControllerLock) {
+ synchronized (mLock) {
mControllerCallbacks.remove(cb);
}
}
@@ -409,9 +612,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public boolean isTransportControlEnabled() throws RemoteException {
+ public boolean isTransportControlEnabled() {
return mTransportPerformerEnabled;
}
+
+ @Override
+ public void showRoutePicker() {
+ mService.showRoutePickerForSession(MediaSessionRecord.this);
+ }
}
private class MessageHandler extends Handler {
@@ -419,6 +627,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
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;
public MessageHandler(Looper looper) {
super(looper);
@@ -438,6 +648,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
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;
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 8fe6055..bc91370 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -17,9 +17,12 @@
package com.android.server.media;
import android.content.Context;
-import android.media.session.IMediaSession;
-import android.media.session.IMediaSessionCallback;
-import android.media.session.IMediaSessionManager;
+import android.media.routeprovider.RouteRequest;
+import android.media.session.ISession;
+import android.media.session.ISessionCallback;
+import android.media.session.ISessionManager;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
@@ -38,21 +41,77 @@ public class MediaSessionService extends SystemService {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SessionManagerImpl mSessionManagerImpl;
+ private final MediaRouteProviderWatcher mRouteProviderWatcher;
private final ArrayList<MediaSessionRecord> mSessions
= new ArrayList<MediaSessionRecord>();
+ private final ArrayList<MediaRouteProviderProxy> mProviders
+ = new ArrayList<MediaRouteProviderProxy>();
private final Object mLock = new Object();
// TODO do we want a separate thread for handling mediasession messages?
private final Handler mHandler = new Handler();
+ // 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;
+
+ // TODO refactor to have per user state. See MediaRouterService for an
+ // example
+
public MediaSessionService(Context context) {
super(context);
mSessionManagerImpl = new SessionManagerImpl();
+ mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback,
+ mHandler, context.getUserId());
}
@Override
public void onStart() {
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
+ mRouteProviderWatcher.start();
+ }
+
+ /**
+ * 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)
+ if (record.getRoute() != null) {
+ // For now send null to mean the local route
+ record.selectRoute(null);
+ return;
+ }
+ mShowRoutesRequestId++;
+ ArrayList<MediaRouteProviderProxy> providers = mRouteProviderWatcher.getProviders();
+ 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) {
+ MediaRouteProviderProxy proxy = 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);
+ // TODO make connect an async call to a ThreadPoolExecutor
+ proxy.connectToRoute(session, route, request);
+ }
}
void sessionDied(MediaSessionRecord session) {
@@ -86,14 +145,14 @@ public class MediaSessionService extends SystemService {
}
private MediaSessionRecord createSessionInternal(int pid, String packageName,
- IMediaSessionCallback cb, String tag) {
+ ISessionCallback cb, String tag) {
synchronized (mLock) {
return createSessionLocked(pid, packageName, cb, tag);
}
}
private MediaSessionRecord createSessionLocked(int pid, String packageName,
- IMediaSessionCallback cb, String tag) {
+ ISessionCallback cb, String tag) {
final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this,
mHandler);
try {
@@ -110,9 +169,82 @@ public class MediaSessionService extends SystemService {
return session;
}
- class SessionManagerImpl extends IMediaSessionManager.Stub {
+ 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 int findIndexOfSessionForIdLocked(String sessionId) {
+ for (int i = mSessions.size() - 1; i >= 0; i--) {
+ MediaSessionRecord session = mSessions.get(i);
+ if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ 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);
+ }
+ }
+ };
+
+ 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 = mSessions.get(index);
+ record.selectRoute(routes.get(0));
+ }
+ }
+ }
+
+ @Override
+ public void onRouteConnected(String sessionId, RouteInfo route,
+ RouteRequest options, RouteConnectionRecord connection) {
+ synchronized (mLock) {
+ int index = findIndexOfSessionForIdLocked(sessionId);
+ if (index != -1) {
+ MediaSessionRecord session = mSessions.get(index);
+ session.setRouteConnected(route, options.getConnectionOptions(), connection);
+ }
+ }
+ }
+ };
+
+ class SessionManagerImpl extends ISessionManager.Stub {
+ // TODO add createSessionAsUser, pass user-id to
+ // ActivityManagerNative.handleIncomingUser and stash result for use
+ // when starting services on that session's behalf.
@Override
- public IMediaSession createSession(String packageName, IMediaSessionCallback cb, String tag)
+ public ISession createSession(String packageName, ISessionCallback cb, String tag)
throws RemoteException {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/media/RouteConnectionRecord.java b/services/core/java/com/android/server/media/RouteConnectionRecord.java
new file mode 100644
index 0000000..8da0f95
--- /dev/null
+++ b/services/core/java/com/android/server/media/RouteConnectionRecord.java
@@ -0,0 +1,108 @@
+/*
+ * 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 Listener mListener;
+
+ public RouteConnectionRecord(IRouteConnection binder) {
+ mBinder = binder;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 747d0a7..df69a6e 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -264,6 +264,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
private static final String LIB_DIR_NAME = "lib";
+ private static final String LIB64_DIR_NAME = "lib64";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
@@ -4318,6 +4319,14 @@ public class PackageManagerService extends IPackageManager.Stub {
private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
PackageParser.Package changingLib) {
+ // We might be upgrading from a version of the platform that did not
+ // provide per-package native library directories for system apps.
+ // Fix that up here.
+ if (isSystemApp(pkg)) {
+ PackageSetting ps = mSettings.mPackages.get(pkg.applicationInfo.packageName);
+ setInternalAppNativeLibraryPath(pkg, ps);
+ }
+
if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
if (mTmpSharedLibraries == null ||
mTmpSharedLibraries.length < mSharedLibraries.size()) {
@@ -5411,10 +5420,26 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ // This is the initial scan-time determination of how to handle a given
+ // package for purposes of native library location.
private void setInternalAppNativeLibraryPath(PackageParser.Package pkg,
PackageSetting pkgSetting) {
- final String apkLibPath = getApkName(pkgSetting.codePathString);
- final String nativeLibraryPath = new File(mAppLibInstallDir, apkLibPath).getPath();
+ // "bundled" here means system-installed with no overriding update
+ final boolean bundledApk = isSystemApp(pkg) && !isUpdatedSystemApp(pkg);
+ final String apkName = getApkName(pkgSetting.codePathString);
+ final File libDir;
+ if (bundledApk) {
+ // If "/system/lib64/apkname" exists, assume that is the per-package
+ // native library directory to use; otherwise use "/system/lib/apkname".
+ File lib64 = new File(Environment.getRootDirectory(), LIB64_DIR_NAME);
+ File packLib64 = new File(lib64, apkName);
+ libDir = (packLib64.exists())
+ ? lib64
+ : new File(Environment.getRootDirectory(), LIB_DIR_NAME);
+ } else {
+ libDir = mAppLibInstallDir;
+ }
+ final String nativeLibraryPath = (new File(libDir, apkName)).getPath();
pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath;
pkgSetting.nativeLibraryPathString = nativeLibraryPath;
}
@@ -9926,13 +9951,14 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// writer
synchronized (mPackages) {
+ PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+ setInternalAppNativeLibraryPath(newPkg, ps);
updatePermissionsLPw(newPkg.packageName, newPkg,
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
if (applyUserRestrictions) {
if (DEBUG_REMOVE) {
Slog.d(TAG, "Propagating install state across reinstall");
}
- PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
for (int i = 0; i < allUserHandles.length; i++) {
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + allUserHandles[i]
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 7d6ba1d..504d471 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -25,6 +25,15 @@
android:name="com.android.onemedia.OnePlayerService"
android:exported="false"
android:process="com.android.onemedia.service" />
+ <service
+ android:name=".provider.OneMediaRouteProvider"
+ android:permission="android.permission.BIND_ROUTE_PROVIDER"
+ android:exported="true"
+ android:process="com.android.onemedia.provider">
+ <intent-filter>
+ <action android:name="com.android.media.session.MediaRouteProvider" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/tests/OneMedia/res/layout/activity_one_player.xml b/tests/OneMedia/res/layout/activity_one_player.xml
index 4208355..516562f 100644
--- a/tests/OneMedia/res/layout/activity_one_player.xml
+++ b/tests/OneMedia/res/layout/activity_one_player.xml
@@ -53,6 +53,12 @@
android:layout_weight="1"
android:text="@string/play_button" />
</LinearLayout>
+ <Button
+ android:id="@+id/route_button"
+ style="@style/BottomBarButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/route_button" />
<TextView
android:id="@+id/status"
android:layout_width="match_parent"
diff --git a/tests/OneMedia/res/values/strings.xml b/tests/OneMedia/res/values/strings.xml
index 1b0cebb..3735c8d 100644
--- a/tests/OneMedia/res/values/strings.xml
+++ b/tests/OneMedia/res/values/strings.xml
@@ -7,6 +7,7 @@
<string name="start_button">Start</string>
<string name="play_button">Play</string>
+ <string name="route_button">Change route</string>
<string name="media_content_hint">Content</string>
<string name="media_next_hint">Next content</string>
<string name="has_video">Is video</string>
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
index 2b14384..189fa6a 100644
--- a/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerCallback.aidl
@@ -15,8 +15,8 @@
package com.android.onemedia;
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
interface IPlayerCallback {
- void onSessionChanged(in MediaSessionToken session);
+ void onSessionChanged(in SessionToken session);
} \ No newline at end of file
diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
index efdbe9a..15ea25f 100644
--- a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
+++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl
@@ -15,14 +15,14 @@
package com.android.onemedia;
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
import android.os.Bundle;
import com.android.onemedia.IPlayerCallback;
import com.android.onemedia.playback.IRequestCallback;
interface IPlayerService {
- MediaSessionToken getSessionToken();
+ SessionToken getSessionToken();
void registerCallback(in IPlayerCallback cb);
void unregisterCallback(in IPlayerCallback cb);
void sendRequest(String action, in Bundle params, in IRequestCallback cb);
diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
index 3114ca9..b9a6470 100644
--- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
+++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java
@@ -37,6 +37,7 @@ public class OnePlayerActivity extends Activity {
private Button mStartButton;
private Button mPlayButton;
+ private Button mRouteButton;
private TextView mStatusView;
private EditText mContentText;
@@ -54,6 +55,7 @@ public class OnePlayerActivity extends Activity {
mStartButton = (Button) findViewById(R.id.start_button);
mPlayButton = (Button) findViewById(R.id.play_button);
+ mRouteButton = (Button) findViewById(R.id.route_button);
mStatusView = (TextView) findViewById(R.id.status);
mContentText = (EditText) findViewById(R.id.content);
mNextContentText = (EditText) findViewById(R.id.next_content);
@@ -61,6 +63,7 @@ public class OnePlayerActivity extends Activity {
mStartButton.setOnClickListener(mButtonListener);
mPlayButton.setOnClickListener(mButtonListener);
+ mRouteButton.setOnClickListener(mButtonListener);
}
@@ -107,6 +110,9 @@ public class OnePlayerActivity extends Activity {
Log.d(TAG, "Start button pressed, in state " + mPlaybackState);
mPlayer.setContent(mContentText.getText().toString());
break;
+ case R.id.route_button:
+ mPlayer.showRoutePicker();
+ break;
}
}
@@ -117,6 +123,7 @@ public class OnePlayerActivity extends Activity {
public void onPlaybackStateChange(PlaybackState state) {
mPlaybackState = state.getState();
boolean enablePlay = false;
+ boolean enableControls = true;
StringBuilder statusBuilder = new StringBuilder();
switch (mPlaybackState) {
case PlaybackState.PLAYSTATE_PLAYING:
@@ -143,12 +150,17 @@ public class OnePlayerActivity extends Activity {
case PlaybackState.PLAYSTATE_NONE:
statusBuilder.append("none");
break;
+ case PlaybackState.PLAYSTATE_CONNECTING:
+ statusBuilder.append("connecting");
+ enableControls = false;
+ break;
default:
statusBuilder.append(mPlaybackState);
}
statusBuilder.append(" -- At position: ").append(state.getPosition());
mStatusView.setText(statusBuilder.toString());
mPlayButton.setEnabled(enablePlay);
+ setControlsEnabled(enableControls);
}
@Override
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index e831ec6..e3f5c0c 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -16,9 +16,10 @@
*/
package com.android.onemedia;
-import android.media.session.MediaController;
+import android.media.session.SessionController;
import android.media.session.MediaMetadata;
-import android.media.session.MediaSessionManager;
+import android.media.session.RouteInfo;
+import android.media.session.SessionManager;
import android.media.session.PlaybackState;
import android.media.session.TransportController;
import android.os.Bundle;
@@ -39,7 +40,7 @@ public class PlayerController {
public static final int STATE_DISCONNECTED = 0;
public static final int STATE_CONNECTED = 1;
- protected MediaController mController;
+ protected SessionController mController;
protected IPlayerService mBinder;
protected TransportController mTransportControls;
@@ -48,7 +49,7 @@ public class PlayerController {
private Listener mListener;
private TransportListener mTransportListener = new TransportListener();
private SessionCallback mControllerCb;
- private MediaSessionManager mManager;
+ private SessionManager mManager;
private Handler mHandler = new Handler();
private boolean mResumed;
@@ -61,7 +62,7 @@ public class PlayerController {
mServiceIntent = serviceIntent;
}
mControllerCb = new SessionCallback();
- mManager = (MediaSessionManager) context
+ mManager = (SessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
mResumed = false;
@@ -121,6 +122,10 @@ public class PlayerController {
}
}
+ public void showRoutePicker() {
+ mController.showRoutePicker();
+ }
+
private void unbindFromService() {
mContext.unbindService(mServiceConnection);
}
@@ -150,7 +155,7 @@ public class PlayerController {
mBinder = IPlayerService.Stub.asInterface(service);
Log.d(TAG, "service is " + service + " binder is " + mBinder);
try {
- mController = MediaController.fromToken(mBinder.getSessionToken());
+ mController = SessionController.fromToken(mBinder.getSessionToken());
} catch (RemoteException e) {
Log.e(TAG, "Error getting session", e);
return;
@@ -171,9 +176,9 @@ public class PlayerController {
}
};
- private class SessionCallback extends MediaController.Callback {
+ private class SessionCallback extends SessionController.Callback {
@Override
- public void onRouteChanged(Bundle route) {
+ public void onRouteChanged(RouteInfo route) {
// TODO
}
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
index 0ad6dd1..8b53ddf 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java
@@ -17,7 +17,7 @@ package com.android.onemedia;
import android.app.Service;
import android.content.Intent;
-import android.media.session.MediaSessionToken;
+import android.media.session.SessionToken;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.IBinder;
@@ -149,7 +149,7 @@ public class PlayerService extends Service {
}
@Override
- public MediaSessionToken getSessionToken() throws RemoteException {
+ public SessionToken getSessionToken() throws RemoteException {
return mSession.getSessionToken();
}
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index a2d7897..5dc3904 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -17,9 +17,13 @@ package com.android.onemedia;
import android.content.Context;
import android.content.Intent;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionToken;
+import android.media.session.Route;
+import android.media.session.RouteInfo;
+import android.media.session.RouteOptions;
+import android.media.session.RoutePlaybackControls;
+import android.media.session.Session;
+import android.media.session.SessionManager;
+import android.media.session.SessionToken;
import android.media.session.PlaybackState;
import android.media.session.TransportPerformer;
import android.os.Bundle;
@@ -27,41 +31,55 @@ import android.util.Log;
import android.view.KeyEvent;
import com.android.onemedia.playback.LocalRenderer;
+import com.android.onemedia.playback.OneMRPRenderer;
import com.android.onemedia.playback.Renderer;
-import com.android.onemedia.playback.RendererFactory;
+import com.android.onemedia.playback.RequestUtils;
+
+import java.util.ArrayList;
public class PlayerSession {
private static final String TAG = "PlayerSession";
- protected MediaSession mSession;
+ protected Session mSession;
protected Context mContext;
- protected RendererFactory mRendererFactory;
- protected LocalRenderer mRenderer;
- protected MediaSession.Callback mCallback;
+ protected Renderer mRenderer;
+ protected Session.Callback mCallback;
protected Renderer.Listener mRenderListener;
protected TransportPerformer mPerformer;
protected PlaybackState mPlaybackState;
protected Listener mListener;
+ protected ArrayList<RouteOptions> mRouteOptions;
+ protected Route mRoute;
+ protected RoutePlaybackControls mRouteControls;
+ protected RouteListener mRouteListener;
+
+ private String mContent;
public PlayerSession(Context context) {
mContext = context;
- mRendererFactory = new RendererFactory();
mRenderer = new LocalRenderer(context, null);
- mCallback = new ControllerCb();
+ mCallback = new SessionCb();
mRenderListener = new RenderListener();
mPlaybackState = new PlaybackState();
mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
| 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();
}
- MediaSessionManager man = (MediaSessionManager) mContext
+ SessionManager man = (SessionManager) mContext
.getSystemService(Context.MEDIA_SESSION_SERVICE);
Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
mSession = man.createSession("OneMedia");
@@ -69,6 +87,7 @@ public class PlayerSession {
mPerformer = mSession.setTransportPerformerEnabled();
mPerformer.addListener(new TransportListener());
mPerformer.setPlaybackState(mPlaybackState);
+ mSession.setRouteOptions(mRouteOptions);
mSession.publish();
}
@@ -86,18 +105,24 @@ public class PlayerSession {
mListener = listener;
}
- public MediaSessionToken getSessionToken() {
+ public SessionToken getSessionToken() {
return mSession.getSessionToken();
}
public void setContent(Bundle request) {
mRenderer.setContent(request);
+ mContent = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
}
public void setNextContent(Bundle request) {
mRenderer.setNextContent(request);
}
+ private void updateState(int newState) {
+ mPlaybackState.setState(newState);
+ mPerformer.setPlaybackState(mPlaybackState);
+ }
+
public interface Listener {
public void onPlayStateChanged(PlaybackState state);
}
@@ -145,7 +170,11 @@ public class PlayerSession {
mPlaybackState.setErrorMessage("unkown state");
break;
}
- mPlaybackState.setPosition(mRenderer.getSeekPosition());
+ if (mRenderer != null) {
+ mPlaybackState.setPosition(mRenderer.getSeekPosition());
+ } else {
+ mPlaybackState.setPosition(-1);
+ }
mPerformer.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
@@ -173,8 +202,7 @@ public class PlayerSession {
}
- private class ControllerCb extends MediaSession.Callback {
-
+ private class SessionCb extends Session.Callback {
@Override
public void onMediaButton(Intent mediaRequestIntent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(mediaRequestIntent.getAction())) {
@@ -192,6 +220,40 @@ public class PlayerSession {
}
}
}
+
+ @Override
+ public void onRequestRouteChange(RouteInfo route) {
+ if (mRenderer != null) {
+ mRenderer.onStop();
+ }
+ if (route == null) {
+ // Use local route
+ mRoute = null;
+ mRenderer = new LocalRenderer(mContext, null);
+ mRenderer.registerListener(mRenderListener);
+ updateState(PlaybackState.PLAYSTATE_NONE);
+ } else {
+ // Use remote route
+ mSession.connect(route, mRouteOptions.get(0));
+ mRenderer = null;
+ updateState(PlaybackState.PLAYSTATE_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.PLAYSTATE_NONE);
+ }
+
+ @Override
+ public void onRouteDisconnected(Route route, int reason) {
+
+ }
}
private class TransportListener extends TransportPerformer.Listener {
@@ -206,4 +268,12 @@ public class PlayerSession {
}
}
+ private class RouteListener extends RoutePlaybackControls.Listener {
+ @Override
+ public void onPlaybackStateChange(int state) {
+ Log.d(TAG, "Updating state to " + state);
+ updateState(state);
+ }
+ }
+
}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
index 7f62f66..c8a8d6c 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/LocalRenderer.java
@@ -1,3 +1,18 @@
+/*
+ * 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.onemedia.playback;
import org.apache.http.Header;
@@ -370,6 +385,8 @@ public class LocalRenderer extends Renderer implements OnPreparedListener,
* Prepares the player for the given playback request. If the holder is null
* it is assumed this is an audio only source. If playOnReady is set to true
* the media will begin playing as soon as it can.
+ *
+ * @see RequestUtils for the set of valid keys.
*/
public void setContent(Bundle request, SurfaceHolder holder) {
String source = request.getString(RequestUtils.EXTRA_KEY_SOURCE);
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
index f9e6794..05516d2 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/MediaItem.java
@@ -1,3 +1,18 @@
+/*
+ * 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.onemedia.playback;
import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
new file mode 100644
index 0000000..9b0a2b2
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java
@@ -0,0 +1,44 @@
+package com.android.onemedia.playback;
+
+import android.media.session.RoutePlaybackControls;
+import android.os.Bundle;
+
+/**
+ * Renderer for communicating with the OneMRP route
+ */
+public class OneMRPRenderer extends Renderer {
+ private final RoutePlaybackControls mControls;
+
+ public OneMRPRenderer(RoutePlaybackControls controls) {
+ super(null, null);
+ mControls = controls;
+ }
+
+ @Override
+ public void setContent(Bundle request) {
+ mControls.playNow(request.getString(RequestUtils.EXTRA_KEY_SOURCE));
+ }
+
+ @Override
+ public boolean onStop() {
+ mControls.pause();
+ return true;
+ }
+
+ @Override
+ public boolean onPlay() {
+ mControls.resume();
+ return true;
+ }
+
+ @Override
+ public boolean onPause() {
+ mControls.pause();
+ return true;
+ }
+
+ @Override
+ public long getSeekPosition() {
+ return -1;
+ }
+}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
index 72d936c..ac9da23 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/PlaybackError.java
@@ -1,3 +1,18 @@
+/*
+ * 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.onemedia.playback;
import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
index 2451bdf..09debcf 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/Renderer.java
@@ -1,3 +1,18 @@
+/*
+ * 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.onemedia.playback;
import android.content.Context;
@@ -77,39 +92,54 @@ public abstract class Renderer {
}
public boolean onPlay() {
- throw new UnsupportedOperationException("play is not supported.");
+ // TODO consider making these log warnings instead of crashes (or
+ // Log.wtf)
+ // throw new UnsupportedOperationException("play is not supported.");
+ return false;
}
public boolean onPause() {
- throw new UnsupportedOperationException("pause is not supported.");
+ // throw new UnsupportedOperationException("pause is not supported.");
+ return false;
}
public boolean onNext() {
- throw new UnsupportedOperationException("next is not supported.");
+ // throw new UnsupportedOperationException("next is not supported.");
+ return false;
}
public boolean onPrevious() {
- throw new UnsupportedOperationException("previous is not supported.");
+ // throw new
+ // UnsupportedOperationException("previous is not supported.");
+ return false;
}
public boolean onStop() {
- throw new UnsupportedOperationException("stop is not supported.");
+ // throw new UnsupportedOperationException("stop is not supported.");
+ return false;
}
public boolean onSeekTo(int time) {
- throw new UnsupportedOperationException("seekTo is not supported.");
+ // throw new UnsupportedOperationException("seekTo is not supported.");
+ return false;
}
public long getSeekPosition() {
- throw new UnsupportedOperationException("getSeekPosition is not supported.");
+ // throw new
+ // UnsupportedOperationException("getSeekPosition is not supported.");
+ return -1;
}
public long getDuration() {
- throw new UnsupportedOperationException("getDuration is not supported.");
+ // throw new
+ // UnsupportedOperationException("getDuration is not supported.");
+ return -1;
}
public int getPlayState() {
- throw new UnsupportedOperationException("getPlayState is not supported.");
+ // throw new
+ // UnsupportedOperationException("getPlayState is not supported.");
+ return 0;
}
public void onDestroy() {
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java b/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
deleted file mode 100644
index f333fce..0000000
--- a/tests/OneMedia/src/com/android/onemedia/playback/RendererFactory.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.android.onemedia.playback;
-
-import android.content.Context;
-import android.media.MediaRouter;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * TODO: Insert description here.
- */
-public class RendererFactory {
- private static final String TAG = "RendererFactory";
-
- public Renderer createRenderer(MediaRouter.RouteInfo route, Context context, Bundle params) {
- if (route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL) {
- return new LocalRenderer(context, params);
- }
- Log.e(TAG, "Unable to create renderer for route of playback type "
- + route.getPlaybackType());
- return null;
- }
-}
diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
index 9b50dad..dd0d982 100644
--- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
+++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java
@@ -1,3 +1,18 @@
+/*
+ * 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.onemedia.playback;
import android.os.Bundle;
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
new file mode 100644
index 0000000..6edcd7d
--- /dev/null
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -0,0 +1,204 @@
+/*
+ * 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.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.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.onemedia.playback.LocalRenderer;
+import com.android.onemedia.playback.Renderer;
+import com.android.onemedia.playback.RequestUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Test of MediaRouteProvider. Show a dummy provider with a simple interface for
+ * playing music.
+ */
+public class OneMediaRouteProvider extends RouteProviderService {
+ private static final String TAG = "OneMRP";
+ private static final boolean DEBUG = true;
+
+ private Renderer mRenderer;
+ private RenderListener mRenderListener;
+ private PlaybackState mPlaybackState;
+ private RouteConnection mConnection;
+ private RoutePlaybackControlsHandler mControls;
+ private String mRouteId;
+ private Handler mHandler;
+
+ @Override
+ public void onCreate() {
+ mHandler = new Handler();
+ mRouteId = UUID.randomUUID().toString();
+ mRenderer = new LocalRenderer(this, null);
+ mRenderListener = new RenderListener();
+ mPlaybackState = new PlaybackState();
+ mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
+ | 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());
+ }
+ }
+ 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;
+ }
+
+ @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 class PlayHandler extends RoutePlaybackControlsHandler.Listener {
+ private final String mRouteId;
+
+ public PlayHandler(String routeId) {
+ mRouteId = routeId;
+ }
+
+ @Override
+ public void playNow(String content, ResultReceiver cb) {
+ if (DEBUG) {
+ Log.d(TAG, "Attempting to play " + content);
+ }
+ // look up the route and send a play command to it
+ Bundle bundle = new Bundle();
+ bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, content);
+ mRenderer.setContent(bundle);
+ RouteInterfaceHandler.sendResult(cb, RouteInterface.RESULT_SUCCESS, null);
+ }
+
+ @Override
+ public boolean resume() {
+ mRenderer.onPlay();
+ return true;
+ }
+
+ @Override
+ public boolean pause() {
+ mRenderer.onPause();
+ return true;
+ }
+ }
+
+ private class RenderListener implements Renderer.Listener {
+
+ @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.PLAYSTATE_ERROR);
+ }
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ if (newState != Renderer.STATE_ERROR) {
+ mPlaybackState.setErrorMessage(null);
+ }
+ switch (newState) {
+ case Renderer.STATE_ENDED:
+ case Renderer.STATE_STOPPED:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
+ break;
+ case Renderer.STATE_INIT:
+ case Renderer.STATE_PREPARING:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
+ break;
+ case Renderer.STATE_ERROR:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ break;
+ case Renderer.STATE_PAUSED:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+ break;
+ case Renderer.STATE_PLAYING:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
+ break;
+ default:
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setErrorMessage("unkown state");
+ break;
+ }
+ mPlaybackState.setPosition(mRenderer.getSeekPosition());
+
+ mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
+ }
+
+ @Override
+ public void onBufferingUpdate(int percent) {
+ }
+
+ @Override
+ public void onFocusLost() {
+ Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+ mPlaybackState.setPosition(mRenderer.getSeekPosition());
+ }
+
+ @Override
+ public void onNextStarted() {
+ }
+ }
+}