summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt12
-rw-r--r--media/java/android/media/AudioManager.java23
-rw-r--r--media/java/android/media/MediaMetadataEditor.java6
-rw-r--r--media/java/android/media/RemoteControlClient.java129
-rw-r--r--media/java/android/media/session/MediaMetadata.java76
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java213
-rw-r--r--media/java/android/media/session/PlaybackState.java213
-rw-r--r--media/java/android/media/session/TransportPerformer.java11
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java31
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerSession.java31
-rw-r--r--tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java21
11 files changed, 664 insertions, 102 deletions
diff --git a/api/current.txt b/api/current.txt
index 7615d4f..0794685 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15103,6 +15103,7 @@ package android.media.routeprovider {
package android.media.session {
public final class MediaMetadata implements android.os.Parcelable {
+ method public boolean containsKey(java.lang.String);
method public int describeContents();
method public android.graphics.Bitmap getBitmap(java.lang.String);
method public long getLong(java.lang.String);
@@ -15118,6 +15119,7 @@ package android.media.session {
field public static final java.lang.String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
field public static final java.lang.String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
field public static final java.lang.String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final java.lang.String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
field public static final java.lang.String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
field public static final java.lang.String METADATA_KEY_DATE = "android.media.metadata.DATE";
field public static final java.lang.String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
@@ -15150,25 +15152,25 @@ package android.media.session {
method public long getBufferPosition();
method public java.lang.String getErrorMessage();
method public long getPosition();
- method public float getSpeed();
+ method public float getRate();
method public int getState();
method public void setActions(long);
method public void setBufferPosition(long);
method public void setErrorMessage(java.lang.String);
- method public void setPosition(long);
- method public void setSpeed(float);
- method public void setState(int);
+ method public void setState(int, long, float);
method public void writeToParcel(android.os.Parcel, int);
field public static final long ACTION_FASTFORWARD = 64L; // 0x40L
field public static final long ACTION_NEXT_ITEM = 32L; // 0x20L
field public static final long ACTION_PAUSE = 2L; // 0x2L
field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
field public static final long ACTION_PREVIOUS_ITEM = 16L; // 0x10L
field public static final long ACTION_RATING = 128L; // 0x80L
field public static final long ACTION_REWIND = 8L; // 0x8L
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
field public static final long ACTION_STOP = 1L; // 0x1L
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
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
@@ -15177,6 +15179,8 @@ package android.media.session {
field public static final int PLAYSTATE_PAUSED = 2; // 0x2
field public static final int PLAYSTATE_PLAYING = 3; // 0x3
field public static final int PLAYSTATE_REWINDING = 5; // 0x5
+ field public static final int PLAYSTATE_SKIPPING_BACKWARDS = 9; // 0x9
+ field public static final int PLAYSTATE_SKIPPING_FORWARDS = 10; // 0xa
field public static final int PLAYSTATE_STOPPED = 1; // 0x1
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8ae06e0..1dcfcb8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.RemoteController.OnClientUpdateListener;
+import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -48,6 +49,12 @@ import java.util.HashMap;
*/
public class AudioManager {
+ // If we should use the new sessions APIs.
+ private final static boolean USE_SESSIONS = true;
+ // If we should use the legacy APIs. If both are true information will be
+ // duplicated through both paths. Currently this flag isn't used.
+ private final static boolean USE_LEGACY = true;
+
private final Context mContext;
private long mVolumeKeyUpTime;
private final boolean mUseMasterVolume;
@@ -421,6 +428,7 @@ public class AudioManager {
public static final int USE_DEFAULT_STREAM_TYPE = Integer.MIN_VALUE;
private static IAudioService sService;
+ private MediaSessionLegacyHelper mSessionHelper;
/**
* @hide
@@ -431,6 +439,9 @@ public class AudioManager {
com.android.internal.R.bool.config_useMasterVolume);
mUseVolumeKeySounds = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useVolumeKeySounds);
+ if (USE_SESSIONS) {
+ mSessionHelper = MediaSessionLegacyHelper.getHelper(context);
+ }
}
private static IAudioService getService()
@@ -2166,6 +2177,9 @@ public class AudioManager {
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerMediaButtonIntent"+e);
}
+ if (USE_SESSIONS) {
+ mSessionHelper.addMediaButtonListener(pi, mContext);
+ }
}
/**
@@ -2239,6 +2253,9 @@ public class AudioManager {
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterMediaButtonIntent"+e);
}
+ if (USE_SESSIONS) {
+ mSessionHelper.removeMediaButtonListener(pi);
+ }
}
/**
@@ -2263,6 +2280,9 @@ public class AudioManager {
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
}
+ if (USE_SESSIONS) {
+ rcClient.registerWithSession(mSessionHelper);
+ }
}
/**
@@ -2282,6 +2302,9 @@ public class AudioManager {
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterRemoteControlClient"+e);
}
+ if (USE_SESSIONS) {
+ rcClient.unregisterWithSession(mSessionHelper);
+ }
}
/**
diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java
index 3bfdb5a..1a4e8da 100644
--- a/media/java/android/media/MediaMetadataEditor.java
+++ b/media/java/android/media/MediaMetadataEditor.java
@@ -17,6 +17,7 @@
package android.media;
import android.graphics.Bitmap;
+import android.media.session.MediaMetadata;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
@@ -106,6 +107,10 @@ public abstract class MediaMetadataEditor {
*/
protected Bundle mEditorMetadata;
+ /**
+ * @hide
+ */
+ protected MediaMetadata.Builder mMetadataBuilder;
/**
* Clears all the pending metadata changes set since the MediaMetadataEditor instance was
@@ -120,6 +125,7 @@ public abstract class MediaMetadataEditor {
}
mEditorMetadata.clear();
mEditorArtwork = null;
+ mMetadataBuilder = new MediaMetadata.Builder();
}
/**
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index c2c61d3..8368df9 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -24,6 +24,11 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
+import android.media.session.MediaMetadata;
+import android.media.session.MediaSessionLegacyHelper;
+import android.media.session.PlaybackState;
+import android.media.session.Session;
+import android.media.session.TransportPerformer;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -336,6 +341,8 @@ public class RemoteControlClient
*/
public final static int FLAG_INFORMATION_REQUEST_ALBUM_ART = 1 << 3;
+ private Session mSession;
+
/**
* Class constructor.
* @param mediaButtonIntent The intent that will be sent for the media button events sent
@@ -385,6 +392,22 @@ public class RemoteControlClient
}
/**
+ * @hide
+ */
+ public void registerWithSession(MediaSessionLegacyHelper helper) {
+ helper.addRccListener(mRcMediaIntent, mTransportListener);
+ mSession = helper.getSession(mRcMediaIntent);
+ }
+
+ /**
+ * @hide
+ */
+ public void unregisterWithSession(MediaSessionLegacyHelper helper) {
+ helper.removeRccListener(mRcMediaIntent);
+ mSession = null;
+ }
+
+ /**
* Class used to modify metadata in a {@link RemoteControlClient} object.
* Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
* on which you set the metadata for the RemoteControlClient instance. Once all the information
@@ -438,6 +461,15 @@ public class RemoteControlClient
public synchronized MetadataEditor putString(int key, String value)
throws IllegalArgumentException {
super.putString(key, value);
+ if (mMetadataBuilder != null) {
+ // MediaMetadata supports all the same fields as MetadataEditor
+ String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
+ // But just in case, don't add things we don't understand
+ if (metadataKey != null) {
+ mMetadataBuilder.putString(metadataKey, value);
+ }
+ }
+
return this;
}
@@ -459,6 +491,14 @@ public class RemoteControlClient
public synchronized MetadataEditor putLong(int key, long value)
throws IllegalArgumentException {
super.putLong(key, value);
+ if (mMetadataBuilder != null) {
+ // MediaMetadata supports all the same fields as MetadataEditor
+ String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
+ // But just in case, don't add things we don't understand
+ if (metadataKey != null) {
+ mMetadataBuilder.putLong(metadataKey, value);
+ }
+ }
return this;
}
@@ -476,6 +516,14 @@ public class RemoteControlClient
public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
throws IllegalArgumentException {
super.putBitmap(key, bitmap);
+ if (mMetadataBuilder != null) {
+ // MediaMetadata supports all the same fields as MetadataEditor
+ String metadataKey = MediaMetadata.getKeyFromMetadataEditorKey(key);
+ // But just in case, don't add things we don't understand
+ if (metadataKey != null) {
+ mMetadataBuilder.putBitmap(metadataKey, bitmap);
+ }
+ }
return this;
}
@@ -501,7 +549,7 @@ public class RemoteControlClient
Log.e(TAG, "Can't apply a previously applied MetadataEditor");
return;
}
- synchronized(mCacheLock) {
+ synchronized (mCacheLock) {
// assign the edited data
mMetadata = new Bundle(mEditorMetadata);
// add the information about editable keys
@@ -521,6 +569,11 @@ public class RemoteControlClient
// send to remote control display if conditions are met
sendArtwork_syncCacheLock(null, 0, 0);
}
+
+ // USE_SESSIONS
+ if (mSession != null && mMetadataBuilder != null) {
+ mSession.getTransportPerformer().setMetadata(mMetadataBuilder.build());
+ }
mApplied = true;
}
}
@@ -546,6 +599,12 @@ public class RemoteControlClient
editor.mMetadataChanged = false;
editor.mArtworkChanged = false;
}
+ // USE_SESSIONS
+ if (startEmpty || mMediaMetadata == null) {
+ editor.mMetadataBuilder = new MediaMetadata.Builder();
+ } else {
+ editor.mMetadataBuilder = new MediaMetadata.Builder(mMediaMetadata);
+ }
return editor;
}
@@ -624,6 +683,15 @@ public class RemoteControlClient
// handle automatic playback position refreshes
initiateCheckForDrift_syncCacheLock();
+
+ // USE_SESSIONS
+ if (mSession != null) {
+ int pbState = PlaybackState.getStateFromRccState(state);
+ mSessionPlaybackState.setState(pbState, hasPosition ?
+ mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN,
+ playbackSpeed);
+ mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState);
+ }
}
}
}
@@ -704,6 +772,13 @@ public class RemoteControlClient
// send to remote control display if conditions are met
sendTransportControlInfo_syncCacheLock(null);
+
+ // USE_SESSIONS
+ if (mSession != null) {
+ mSessionPlaybackState.setActions(PlaybackState
+ .getActionsFromRccControlFlags(transportControlFlags));
+ mSession.getTransportPerformer().setPlaybackState(mSessionPlaybackState);
+ }
}
}
@@ -1038,6 +1113,16 @@ public class RemoteControlClient
private boolean mNeedsPositionSync = false;
/**
+ * Cache for the current playback state using Session APIs.
+ */
+ private final PlaybackState mSessionPlaybackState = new PlaybackState();
+
+ /**
+ * Cache for metadata using Session APIs. This is re-initialized in apply().
+ */
+ private MediaMetadata mMediaMetadata;
+
+ /**
* A class to encapsulate all the information about a remote control display.
* A RemoteControlClient's metadata and state may be displayed on multiple IRemoteControlDisplay
*/
@@ -1219,6 +1304,26 @@ public class RemoteControlClient
return mRcseId;
}
+ // USE_SESSIONS
+ private TransportPerformer.Listener mTransportListener = new TransportPerformer.Listener() {
+
+ @Override
+ public void onSeekTo(long pos) {
+ RemoteControlClient.this.onSeekTo(mCurrentClientGenId, pos);
+ }
+
+ @Override
+ public void onRate(Rating rating) {
+ if ((mTransportControlFlags & FLAG_KEY_MEDIA_RATING) != 0) {
+ if (mEventHandler != null) {
+ mEventHandler.sendMessage(mEventHandler.obtainMessage(
+ MSG_UPDATE_METADATA, mCurrentClientGenId,
+ MetadataEditor.RATING_KEY_BY_USER, rating));
+ }
+ }
+ }
+ };
+
private EventHandler mEventHandler;
private final static int MSG_REQUEST_PLAYBACK_STATE = 1;
private final static int MSG_REQUEST_METADATA = 2;
@@ -1325,7 +1430,7 @@ public class RemoteControlClient
// target == null implies all displays must be updated
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mEnabled) {
try {
di.mRcDisplay.setPlaybackState(mInternalClientGenId,
@@ -1353,7 +1458,7 @@ public class RemoteControlClient
// target == null implies all displays must be updated
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mEnabled) {
try {
di.mRcDisplay.setMetadata(mInternalClientGenId, mMetadata);
@@ -1381,7 +1486,7 @@ public class RemoteControlClient
// target == null implies all displays must be updated
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mEnabled) {
try {
di.mRcDisplay.setTransportControlInfo(mInternalClientGenId,
@@ -1407,7 +1512,7 @@ public class RemoteControlClient
// target == null implies all displays must be updated
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- if (!sendArtworkToDisplay((DisplayInfoForClient) displayIterator.next())) {
+ if (!sendArtworkToDisplay(displayIterator.next())) {
displayIterator.remove();
}
}
@@ -1453,7 +1558,7 @@ public class RemoteControlClient
// target == null implies all displays must be updated
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
try {
if (di.mEnabled) {
if ((di.mArtworkExpectedWidth > 0) && (di.mArtworkExpectedHeight > 0)) {
@@ -1537,7 +1642,7 @@ public class RemoteControlClient
boolean displayKnown = false;
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext() && !displayKnown) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
displayKnown = di.mRcDisplay.asBinder().equals(rcd.asBinder());
if (displayKnown) {
// this display was known but the change in artwork size will cause the
@@ -1562,7 +1667,7 @@ public class RemoteControlClient
synchronized(mCacheLock) {
Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
displayIterator.remove();
break;
@@ -1573,7 +1678,7 @@ public class RemoteControlClient
boolean newNeedsPositionSync = false;
displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mWantsPositionSync) {
newNeedsPositionSync = true;
break;
@@ -1592,7 +1697,7 @@ public class RemoteControlClient
synchronized(mCacheLock) {
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder()) &&
((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h))) {
di.mArtworkExpectedWidth = w;
@@ -1617,7 +1722,7 @@ public class RemoteControlClient
// go through the list of RCDs and for each entry, check both whether this is the RCD
// that gets upated, and whether the list has one entry that wants position sync
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mEnabled) {
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
di.mWantsPositionSync = wantsSync;
@@ -1640,7 +1745,7 @@ public class RemoteControlClient
synchronized(mCacheLock) {
final Iterator<DisplayInfoForClient> displayIterator = mRcDisplays.iterator();
while (displayIterator.hasNext()) {
- final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
+ final DisplayInfoForClient di = displayIterator.next();
if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
di.mEnabled = enable;
}
diff --git a/media/java/android/media/session/MediaMetadata.java b/media/java/android/media/session/MediaMetadata.java
index e2330f7..56bdf68 100644
--- a/media/java/android/media/session/MediaMetadata.java
+++ b/media/java/android/media/session/MediaMetadata.java
@@ -16,12 +16,15 @@
package android.media.session;
import android.graphics.Bitmap;
+import android.media.MediaMetadataEditor;
+import android.media.MediaMetadataRetriever;
import android.media.Rating;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
/**
* Contains metadata about an item, such as the title, artist, etc.
@@ -40,7 +43,8 @@ public final class MediaMetadata implements Parcelable {
public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
/**
- * The duration of the media in ms. A duration of 0 is the default.
+ * The duration of the media in ms. A negative duration indicates that the
+ * duration is unknown (or infinite).
*/
public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
@@ -65,12 +69,17 @@ public final class MediaMetadata implements Parcelable {
public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
/**
+ * The compilation status of the media.
+ */
+ public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+
+ /**
* The date the media was created or published as TODO determine format.
*/
public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
/**
- * The year the media was created or published as a numeric String.
+ * The year the media was created or published as a long.
*/
public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
@@ -151,8 +160,9 @@ public final class MediaMetadata implements Parcelable {
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_STRING);
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_STRING);
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_STRING);
- METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_STRING);
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
@@ -165,6 +175,36 @@ public final class MediaMetadata implements Parcelable {
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
}
+
+ private static final SparseArray<String> EDITOR_KEY_MAPPING;
+
+ static {
+ EDITOR_KEY_MAPPING = new SparseArray<String>();
+ EDITOR_KEY_MAPPING.put(MediaMetadataEditor.BITMAP_KEY_ARTWORK, METADATA_KEY_ART);
+ EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_OTHERS, METADATA_KEY_RATING);
+ EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_USER, METADATA_KEY_USER_RATING);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_KEY_ALBUM);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
+ METADATA_KEY_ALBUM_ARTIST);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_KEY_ARTIST);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_KEY_AUTHOR);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
+ METADATA_KEY_TRACK_NUMBER);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_KEY_COMPOSER);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
+ METADATA_KEY_COMPILATION);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_KEY_DATE);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
+ METADATA_KEY_DISC_NUMBER);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_KEY_DURATION);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_KEY_GENRE);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
+ METADATA_KEY_NUM_TRACKS);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_KEY_TITLE);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_KEY_WRITER);
+ EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_KEY_YEAR);
+ }
+
private final Bundle mBundle;
private MediaMetadata(Bundle bundle) {
@@ -176,6 +216,16 @@ public final class MediaMetadata implements Parcelable {
}
/**
+ * Returns true if the given key is contained in the metadata
+ *
+ * @param key a String key
+ * @return true if the key exists in this metadata, false otherwise
+ */
+ public boolean containsKey(String key) {
+ return mBundle.containsKey(key);
+ }
+
+ /**
* Returns the value associated with the given key, or null if no mapping of
* the desired type exists for the given key or a null value is explicitly
* associated with the key.
@@ -195,7 +245,7 @@ public final class MediaMetadata implements Parcelable {
* @return a long value
*/
public long getLong(String key) {
- return mBundle.getLong(key);
+ return mBundle.getLong(key, 0);
}
/**
@@ -244,6 +294,18 @@ public final class MediaMetadata implements Parcelable {
dest.writeBundle(mBundle);
}
+ /**
+ * Helper for getting the String key used by {@link MediaMetadata} from the
+ * integer key that {@link MediaMetadataEditor} uses.
+ *
+ * @param editorKey The key used by the editor
+ * @return The key used by this class or null if no mapping exists
+ * @hide
+ */
+ public static String getKeyFromMetadataEditorKey(int editorKey) {
+ return EDITOR_KEY_MAPPING.get(editorKey, null);
+ }
+
public static final Parcelable.Creator<MediaMetadata> CREATOR
= new Parcelable.Creator<MediaMetadata>() {
@Override
@@ -295,10 +357,9 @@ public final class MediaMetadata implements Parcelable {
* <li>{@link #METADATA_KEY_WRITER}</li>
* <li>{@link #METADATA_KEY_COMPOSER}</li>
* <li>{@link #METADATA_KEY_DATE}</li>
- * <li>{@link #METADATA_KEY_YEAR}</li>
* <li>{@link #METADATA_KEY_GENRE}</li>
- * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>li>
- * <li>{@link #METADATA_KEY_ART_URI}</li>li>
+ * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ART_URI}</li>
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
* </ul>
*
@@ -326,6 +387,7 @@ public final class MediaMetadata implements Parcelable {
* <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
* <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
* <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+ * <li>{@link #METADATA_KEY_YEAR}</li>
* </ul>
*
* @param key The key for referencing this value
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
new file mode 100644
index 0000000..4ee67d1
--- /dev/null
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -0,0 +1,213 @@
+/*
+ * 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.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.KeyEvent;
+
+/**
+ * Helper for connecting existing APIs up to the new session APIs. This can be
+ * used by RCC, AudioFocus, etc. to create a single session that translates to
+ * all those components.
+ *
+ * @hide
+ */
+public class MediaSessionLegacyHelper {
+ private static final String TAG = "MediaSessionHelper";
+
+ private static final Object sLock = new Object();
+ private static MediaSessionLegacyHelper sInstance;
+
+ private SessionManager mSessionManager;
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ // The legacy APIs use PendingIntents to register/unregister media button
+ // receivers and these are associated with RCC.
+ private ArrayMap<PendingIntent, SessionHolder> mSessions = new ArrayMap<PendingIntent, SessionHolder>();
+
+ private MediaSessionLegacyHelper(Context context) {
+ mSessionManager = (SessionManager) context
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ }
+
+ public static MediaSessionLegacyHelper getHelper(Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new MediaSessionLegacyHelper(context);
+ }
+ }
+ return sInstance;
+ }
+
+ public Session getSession(PendingIntent pi) {
+ SessionHolder holder = mSessions.get(pi);
+ return holder == null ? null : holder.mSession;
+ }
+
+ public void addRccListener(PendingIntent pi, TransportPerformer.Listener listener) {
+
+ SessionHolder holder = getHolder(pi, true);
+ TransportPerformer performer = holder.mSession.getTransportPerformer();
+ if (holder.mRccListener != null) {
+ if (holder.mRccListener == listener) {
+ // This is already the registered listener, ignore
+ return;
+ }
+ // Otherwise it changed so we need to switch to the new one
+ performer.removeListener(holder.mRccListener);
+ }
+ performer.addListener(listener, mHandler);
+ holder.mRccListener = listener;
+ holder.update();
+ }
+
+ public void removeRccListener(PendingIntent pi) {
+ SessionHolder holder = getHolder(pi, false);
+ if (holder != null && holder.mRccListener != null) {
+ holder.mSession.getTransportPerformer().removeListener(holder.mRccListener);
+ holder.mRccListener = null;
+ holder.update();
+ }
+ }
+
+ public void addMediaButtonListener(PendingIntent pi,
+ Context context) {
+ SessionHolder holder = getHolder(pi, true);
+ if (holder.mMediaButtonListener != null) {
+ // Already have this listener registered
+ return;
+ }
+ holder.mMediaButtonListener = new MediaButtonListener(pi, context);
+ holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler);
+ }
+
+ public void removeMediaButtonListener(PendingIntent pi) {
+ SessionHolder holder = getHolder(pi, false);
+ if (holder != null && holder.mMediaButtonListener != null) {
+ holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener);
+ holder.update();
+ }
+ }
+
+ private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
+ SessionHolder holder = mSessions.get(pi);
+ if (holder == null && createIfMissing) {
+ Session session = mSessionManager.createSession(TAG);
+ session.setTransportPerformerEnabled();
+ session.publish();
+ holder = new SessionHolder(session, pi);
+ mSessions.put(pi, holder);
+ }
+ return holder;
+ }
+
+ public static class MediaButtonListener extends TransportPerformer.Listener {
+ private final PendingIntent mPendingIntent;
+ private final Context mContext;
+
+ public MediaButtonListener(PendingIntent pi, Context context) {
+ mPendingIntent = pi;
+ mContext = context;
+ }
+
+ @Override
+ public void onPlay() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY);
+ }
+
+ @Override
+ public void onPause() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE);
+ }
+
+ @Override
+ public void onNext() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
+ }
+
+ @Override
+ public void onPrevious() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+ }
+
+ @Override
+ public void onFastForward() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+ }
+
+ @Override
+ public void onRewind() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_REWIND);
+ }
+
+ @Override
+ public void onStop() {
+ sendKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP);
+ }
+
+ private void sendKeyEvent(int keyCode) {
+ KeyEvent ke = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
+ try {
+ mPendingIntent.send(mContext, 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending media key down event:", e);
+ // Don't bother sending up if down failed
+ return;
+ }
+
+ ke = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, ke);
+ try {
+ mPendingIntent.send(mContext, 0, intent);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending media key up event:", e);
+ }
+ }
+ }
+
+ private class SessionHolder {
+ public final Session mSession;
+ public final PendingIntent mPi;
+ public MediaButtonListener mMediaButtonListener;
+ public TransportPerformer.Listener mRccListener;
+
+ public SessionHolder(Session session, PendingIntent pi) {
+ mSession = session;
+ mPi = pi;
+ }
+
+ public void update() {
+ if (mMediaButtonListener == null && mRccListener == null) {
+ mSession.release();
+ mSessions.remove(mPi);
+ } else if (mMediaButtonListener != null && mRccListener != null) {
+ // TODO set session to active
+ } else {
+ // TODO set session to inactive
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 14d9fb1..9e58ea8 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,8 +15,10 @@
*/
package android.media.session;
+import android.media.RemoteControlClient;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
/**
* Playback state for a {@link Session}. This includes a state like
@@ -88,6 +90,13 @@ public final class PlaybackState implements Parcelable {
public static final long ACTION_SEEK_TO = 1 << 8;
/**
+ * Indicates this performer supports the play/pause toggle command.
+ *
+ * @see #setActions
+ */
+ public static final long ACTION_PLAY_PAUSE = 1 << 9;
+
+ /**
* This is the default playback state and indicates that no media has been
* added yet, or the performer has been reset and has no content to play.
*
@@ -154,12 +163,33 @@ public final class PlaybackState implements Parcelable {
*/
public final static int PLAYSTATE_CONNECTING = 8;
+ /**
+ * State indicating the player is currently skipping to the previous item.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_SKIPPING_BACKWARDS = 9;
+
+ /**
+ * State indicating the player is currently skipping to the next item.
+ *
+ * @see #setState
+ */
+ public final static int PLAYSTATE_SKIPPING_FORWARDS = 10;
+
+ /**
+ * Set this value on {@link #setPosition(long)} to indicate the position is
+ * not known for this item.
+ */
+ public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+
private int mState;
private long mPosition;
private long mBufferPosition;
- private float mSpeed;
- private long mCapabilities;
+ private float mRate;
+ private long mActions;
private String mErrorMessage;
+ private long mUpdateTime;
/**
* Create an empty PlaybackState. At minimum a state and actions should be
@@ -175,21 +205,24 @@ public final class PlaybackState implements Parcelable {
* @param from The PlaybackState to duplicate
*/
public PlaybackState(PlaybackState from) {
- this.setState(from.getState());
- this.setPosition(from.getPosition());
- this.setBufferPosition(from.getBufferPosition());
- this.setSpeed(from.getSpeed());
- this.setActions(from.getActions());
- this.setErrorMessage(from.getErrorMessage());
+ mState = from.mState;
+ mPosition = from.mPosition;
+ mRate = from.mRate;
+ mUpdateTime = from.mUpdateTime;
+ mBufferPosition = from.mBufferPosition;
+ mActions = from.mActions;
+ mErrorMessage = from.mErrorMessage;
}
private PlaybackState(Parcel in) {
- this.setState(in.readInt());
- this.setPosition(in.readLong());
- this.setBufferPosition(in.readLong());
- this.setSpeed(in.readFloat());
- this.setActions(in.readLong());
- this.setErrorMessage(in.readString());
+ mState = in.readInt();
+ mPosition = in.readLong();
+ mRate = in.readFloat();
+ mUpdateTime = in.readLong();
+ mBufferPosition = in.readLong();
+ mActions = in.readLong();
+ mErrorMessage = in.readString();
+
}
@Override
@@ -199,12 +232,13 @@ public final class PlaybackState implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(getState());
- dest.writeLong(getPosition());
- dest.writeLong(getBufferPosition());
- dest.writeFloat(getSpeed());
- dest.writeLong(getActions());
- dest.writeString(getErrorMessage());
+ dest.writeInt(mState);
+ dest.writeLong(mPosition);
+ dest.writeFloat(mRate);
+ dest.writeLong(mUpdateTime);
+ dest.writeLong(mBufferPosition);
+ dest.writeLong(mActions);
+ dest.writeString(mErrorMessage);
}
/**
@@ -224,7 +258,16 @@ public final class PlaybackState implements Parcelable {
}
/**
- * Set the current state of playback. One of the following:
+ * Set the current state of playback.
+ * <p>
+ * The position must be in ms and indicates the current playback position
+ * within the track. If the position is unknown use
+ * {@link #PLAYBACK_POSITION_UNKNOWN}.
+ * <p>
+ * The rate is a multiple of normal playback and should be 0 when paused and
+ * negative when rewinding. Normal playback rate is 1.0.
+ * <p>
+ * The state must be one of the following:
* <ul>
* <li> {@link PlaybackState#PLAYSTATE_NONE}</li>
* <li> {@link PlaybackState#PLAYSTATE_STOPPED}</li>
@@ -234,9 +277,18 @@ public final class PlaybackState implements Parcelable {
* <li> {@link PlaybackState#PLAYSTATE_REWINDING}</li>
* <li> {@link PlaybackState#PLAYSTATE_BUFFERING}</li>
* <li> {@link PlaybackState#PLAYSTATE_ERROR}</li>
- */
- public void setState(int mState) {
- this.mState = mState;
+ * </ul>
+ *
+ * @param state The current state of playback.
+ * @param position The position in the current track in ms.
+ * @param rate The current rate of playback as a multiple of normal
+ * playback.
+ */
+ public void setState(int state, long position, float rate) {
+ this.mState = state;
+ this.mPosition = position;
+ this.mRate = rate;
+ mUpdateTime = SystemClock.elapsedRealtime();
}
/**
@@ -247,13 +299,6 @@ public final class PlaybackState implements Parcelable {
}
/**
- * Set the current playback position in ms.
- */
- public void setPosition(long position) {
- mPosition = position;
- }
-
- /**
* Get the current buffer position in ms. This is the farthest playback
* point that can be reached from the current position using only buffered
* content.
@@ -272,21 +317,14 @@ public final class PlaybackState implements Parcelable {
}
/**
- * Get the current playback speed as a multiple of normal playback. This
- * should be negative when rewinding. A value of 1 means normal playback and
- * 0 means paused.
- */
- public float getSpeed() {
- return mSpeed;
- }
-
- /**
- * Set the current playback speed as a multiple of normal playback. This
+ * Get the current playback rate as a multiple of normal playback. This
* should be negative when rewinding. A value of 1 means normal playback and
* 0 means paused.
+ *
+ * @return The current rate of playback.
*/
- public void setSpeed(float speed) {
- mSpeed = speed;
+ public float getRate() {
+ return mRate;
}
/**
@@ -305,7 +343,7 @@ public final class PlaybackState implements Parcelable {
* </ul>
*/
public long getActions() {
- return mCapabilities;
+ return mActions;
}
/**
@@ -324,7 +362,7 @@ public final class PlaybackState implements Parcelable {
* </ul>
*/
public void setActions(long capabilities) {
- mCapabilities = capabilities;
+ mActions = capabilities;
}
/**
@@ -336,6 +374,17 @@ public final class PlaybackState implements Parcelable {
}
/**
+ * Get the elapsed real time at which position was last updated. If the
+ * position has never been set this will return 0;
+ *
+ * @return The last time the position was updated.
+ * @hide
+ */
+ public long getLastPositionUpdateTime() {
+ return mUpdateTime;
+ }
+
+ /**
* Set a user readable error message. This should be set when the state is
* {@link PlaybackState#PLAYSTATE_ERROR}.
*/
@@ -343,6 +392,80 @@ public final class PlaybackState implements Parcelable {
mErrorMessage = errorMessage;
}
+ /**
+ * Get the {@link PlaybackState} state for the given
+ * {@link RemoteControlClient} state.
+ *
+ * @param rccState The state used by {@link RemoteControlClient}.
+ * @return The equivalent state used by {@link PlaybackState}.
+ * @hide
+ */
+ public static int getStateFromRccState(int rccState) {
+ switch (rccState) {
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ return PLAYSTATE_BUFFERING;
+ case RemoteControlClient.PLAYSTATE_ERROR:
+ return PLAYSTATE_ERROR;
+ case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+ return PLAYSTATE_FAST_FORWARDING;
+ case RemoteControlClient.PLAYSTATE_NONE:
+ return PLAYSTATE_NONE;
+ case RemoteControlClient.PLAYSTATE_PAUSED:
+ return PLAYSTATE_PAUSED;
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ return PLAYSTATE_PLAYING;
+ case RemoteControlClient.PLAYSTATE_REWINDING:
+ return PLAYSTATE_REWINDING;
+ case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ return PLAYSTATE_SKIPPING_BACKWARDS;
+ case RemoteControlClient.PLAYSTATE_STOPPED:
+ return PLAYSTATE_STOPPED;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static long getActionsFromRccControlFlags(int rccFlags) {
+ long actions = 0;
+ long flag = 1;
+ while (flag <= rccFlags) {
+ if ((flag & rccFlags) != 0) {
+ actions |= getActionForRccFlag((int) flag);
+ }
+ flag = flag << 1;
+ }
+ return actions;
+ }
+
+ private static long getActionForRccFlag(int flag) {
+ switch (flag) {
+ case RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS:
+ return ACTION_PREVIOUS_ITEM;
+ case RemoteControlClient.FLAG_KEY_MEDIA_REWIND:
+ return ACTION_REWIND;
+ case RemoteControlClient.FLAG_KEY_MEDIA_PLAY:
+ return ACTION_PLAY;
+ case RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE:
+ return ACTION_PLAY_PAUSE;
+ case RemoteControlClient.FLAG_KEY_MEDIA_PAUSE:
+ return ACTION_PAUSE;
+ case RemoteControlClient.FLAG_KEY_MEDIA_STOP:
+ return ACTION_STOP;
+ case RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD:
+ return ACTION_FASTFORWARD;
+ case RemoteControlClient.FLAG_KEY_MEDIA_NEXT:
+ return ACTION_NEXT_ITEM;
+ case RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE:
+ return ACTION_SEEK_TO;
+ case RemoteControlClient.FLAG_KEY_MEDIA_RATING:
+ return ACTION_RATING;
+ }
+ return 0;
+ }
+
public static final Parcelable.Creator<PlaybackState> CREATOR
= new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java
index eddffd1..187f48d 100644
--- a/media/java/android/media/session/TransportPerformer.java
+++ b/media/java/android/media/session/TransportPerformer.java
@@ -280,18 +280,11 @@ public final class TransportPerformer {
/**
* Report that audio focus has changed on the app. This only happens if
* you have indicated you have started playing with
- * {@link #setPlaybackState}. TODO figure out route focus apis/handling.
+ * {@link #setPlaybackState}.
*
- * @param focusChange The type of focus change, TBD. The default
- * implementation will deliver a call to {@link #onPause}
- * when focus is lost.
+ * @param focusChange The type of focus change, TBD.
*/
public void onRouteFocusChange(int focusChange) {
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- onPause();
- break;
- }
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 2a1e2c3..ab2a723 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -40,6 +40,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -323,6 +324,34 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
+ private PlaybackState getStateWithUpdatedPosition() {
+ PlaybackState state = mPlaybackState;
+ long duration = -1;
+ if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ PlaybackState result = null;
+ if (state != null) {
+ if (state.getState() == PlaybackState.PLAYSTATE_PLAYING
+ || state.getState() == PlaybackState.PLAYSTATE_FAST_FORWARDING
+ || state.getState() == PlaybackState.PLAYSTATE_REWINDING) {
+ long updateTime = state.getLastPositionUpdateTime();
+ if (updateTime > 0) {
+ long position = (long) (state.getRate()
+ * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
+ if (duration >= 0 && position > duration) {
+ position = duration;
+ } else if (position < 0) {
+ position = 0;
+ }
+ result = new PlaybackState(state);
+ result.setState(state.getState(), position, state.getRate());
+ }
+ }
+ }
+ return result == null ? state : result;
+ }
+
private final RouteConnectionRecord.Listener mConnectionListener
= new RouteConnectionRecord.Listener() {
@Override
@@ -625,7 +654,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
@Override
public PlaybackState getPlaybackState() {
- return mPlaybackState;
+ return getStateWithUpdatedPosition();
}
@Override
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 5dc3904..2e029f0 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -119,7 +119,9 @@ public class PlayerSession {
}
private void updateState(int newState) {
- mPlaybackState.setState(newState);
+ float rate = newState == PlaybackState.PLAYSTATE_PLAYING ? 1 : 0;
+ long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
+ mPlaybackState.setState(newState, position, rate);
mPerformer.setPlaybackState(mPlaybackState);
}
@@ -132,7 +134,7 @@ public class PlayerSession {
@Override
public void onError(int type, int extra, Bundle extras, Throwable error) {
Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
- mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, -1, 0);
if (error != null) {
mPlaybackState.setErrorMessage(error.getLocalizedMessage());
}
@@ -147,34 +149,33 @@ public class PlayerSession {
if (newState != Renderer.STATE_ERROR) {
mPlaybackState.setErrorMessage(null);
}
+ long position = -1;
+ if (mRenderer != null) {
+ position = mRenderer.getSeekPosition();
+ }
switch (newState) {
case Renderer.STATE_ENDED:
case Renderer.STATE_STOPPED:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0);
break;
case Renderer.STATE_INIT:
case Renderer.STATE_PREPARING:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0);
break;
case Renderer.STATE_ERROR:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
break;
case Renderer.STATE_PAUSED:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
break;
case Renderer.STATE_PLAYING:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1);
break;
default:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
mPlaybackState.setErrorMessage("unkown state");
break;
}
- if (mRenderer != null) {
- mPlaybackState.setPosition(mRenderer.getSeekPosition());
- } else {
- mPlaybackState.setPosition(-1);
- }
mPerformer.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
@@ -188,8 +189,8 @@ public class PlayerSession {
@Override
public void onFocusLost() {
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
- mPlaybackState.setPosition(mRenderer.getSeekPosition());
+ long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
mPerformer.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
index 6edcd7d..6537d49 100644
--- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -158,30 +158,33 @@ public class OneMediaRouteProvider extends RouteProviderService {
if (newState != Renderer.STATE_ERROR) {
mPlaybackState.setErrorMessage(null);
}
+ long position = -1;
+ if (mRenderer != null) {
+ position = mRenderer.getSeekPosition();
+ }
switch (newState) {
case Renderer.STATE_ENDED:
case Renderer.STATE_STOPPED:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_STOPPED, position, 0);
break;
case Renderer.STATE_INIT:
case Renderer.STATE_PREPARING:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_BUFFERING, position, 0);
break;
case Renderer.STATE_ERROR:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
break;
case Renderer.STATE_PAUSED:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, position, 0);
break;
case Renderer.STATE_PLAYING:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PLAYING, position, 1);
break;
default:
- mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR);
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_ERROR, position, 0);
mPlaybackState.setErrorMessage("unkown state");
break;
}
- mPlaybackState.setPosition(mRenderer.getSeekPosition());
mControls.sendPlaybackChangeEvent(mPlaybackState.getState());
}
@@ -193,8 +196,8 @@ public class OneMediaRouteProvider extends RouteProviderService {
@Override
public void onFocusLost() {
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
- mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED);
- mPlaybackState.setPosition(mRenderer.getSeekPosition());
+ mPlaybackState.setState(PlaybackState.PLAYSTATE_PAUSED, mRenderer.getSeekPosition(), 0);
+ mRenderer.onPause();
}
@Override