From f1372428f2df781c71c71caa2f6a4db6f847cf10 Mon Sep 17 00:00:00 2001 From: RoboErik Date: Wed, 23 Apr 2014 14:38:17 -0700 Subject: [PATCH] Add Session API calls to RCC and AudioManager This makes RCC and MediaButtonReceiver (via AudioManager) also use the new Session APIs in parallel to their existing code. This will allow us to bring up the Session compatibility pieces without disrupting the old behavior and then switch everything over to just using the new APIs when ready. Change-Id: I33ce0a044dea3ec763f2302b91a5e415be27d4a4 --- api/current.txt | 12 +- media/java/android/media/AudioManager.java | 23 ++ .../android/media/MediaMetadataEditor.java | 6 + .../android/media/RemoteControlClient.java | 129 ++++++++++- .../android/media/session/MediaMetadata.java | 76 ++++++- .../session/MediaSessionLegacyHelper.java | 213 ++++++++++++++++++ .../android/media/session/PlaybackState.java | 211 +++++++++++++---- .../media/session/TransportPerformer.java | 11 +- .../server/media/MediaSessionRecord.java | 31 ++- .../com/android/onemedia/PlayerSession.java | 31 +-- .../provider/OneMediaRouteProvider.java | 21 +- 11 files changed, 663 insertions(+), 101 deletions(-) create mode 100644 media/java/android/media/session/MediaSessionLegacyHelper.java diff --git a/api/current.txt b/api/current.txt index 1055c24d1179d..1865a52562145 100644 --- a/api/current.txt +++ b/api/current.txt @@ -15029,6 +15029,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); @@ -15044,6 +15045,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"; @@ -15076,25 +15078,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 @@ -15103,6 +15105,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 8ae06e089915c..1dcfcb892842a 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 3bfdb5a800c40..1a4e8da6e4b32 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 c2c61d3ba4bb1..8368df9462909 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 @@ -384,6 +391,22 @@ public class RemoteControlClient mEventHandler = new EventHandler(this, looper); } + /** + * @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, @@ -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); + } } } @@ -1037,6 +1112,16 @@ public class RemoteControlClient // TODO consider using a ref count for IRemoteControlDisplay requiring sync instead 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 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 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 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 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 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 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 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 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 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 e2330f7c54e35..56bdf6805b630 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"; @@ -64,13 +68,18 @@ 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 EDITOR_KEY_MAPPING; + + static { + EDITOR_KEY_MAPPING = new SparseArray(); + 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) { @@ -175,6 +215,16 @@ public final class MediaMetadata implements Parcelable { mBundle = in.readBundle(); } + /** + * 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 @@ -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 CREATOR = new Parcelable.Creator() { @Override @@ -295,10 +357,9 @@ public final class MediaMetadata implements Parcelable { *
  • {@link #METADATA_KEY_WRITER}
  • *
  • {@link #METADATA_KEY_COMPOSER}
  • *
  • {@link #METADATA_KEY_DATE}
  • - *
  • {@link #METADATA_KEY_YEAR}
  • *
  • {@link #METADATA_KEY_GENRE}
  • - *
  • {@link #METADATA_KEY_ALBUM_ARTIST}
  • li> - *
  • {@link #METADATA_KEY_ART_URI}
  • li> + *
  • {@link #METADATA_KEY_ALBUM_ARTIST}
  • + *
  • {@link #METADATA_KEY_ART_URI}
  • *
  • {@link #METADATA_KEY_ALBUM_ART_URI}
  • * * @@ -326,6 +387,7 @@ public final class MediaMetadata implements Parcelable { *
  • {@link #METADATA_KEY_TRACK_NUMBER}
  • *
  • {@link #METADATA_KEY_NUM_TRACKS}
  • *
  • {@link #METADATA_KEY_DISC_NUMBER}
  • + *
  • {@link #METADATA_KEY_YEAR}
  • * * * @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 0000000000000..4ee67d1401851 --- /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 mSessions = new ArrayMap(); + + 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 14d9fb18dbad2..9e58ea81814bf 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 @@ -87,6 +89,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. + *

    + * 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}. + *

    + * The rate is a multiple of normal playback and should be 0 when paused and + * negative when rewinding. Normal playback rate is 1.0. + *

    + * The state must be one of the following: *

      *
    • {@link PlaybackState#PLAYSTATE_NONE}
    • *
    • {@link PlaybackState#PLAYSTATE_STOPPED}
    • @@ -234,9 +277,18 @@ public final class PlaybackState implements Parcelable { *
    • {@link PlaybackState#PLAYSTATE_REWINDING}
    • *
    • {@link PlaybackState#PLAYSTATE_BUFFERING}
    • *
    • {@link PlaybackState#PLAYSTATE_ERROR}
    • + *
    + * + * @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 mState) { - this.mState = mState; + public void setState(int state, long position, float rate) { + this.mState = state; + this.mPosition = position; + this.mRate = rate; + mUpdateTime = SystemClock.elapsedRealtime(); } /** @@ -246,13 +298,6 @@ public final class PlaybackState implements Parcelable { return mPosition; } - /** - * 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 @@ -272,21 +317,14 @@ public final class PlaybackState implements Parcelable { } /** - * Get 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 float getSpeed() { - return mSpeed; - } - - /** - * Set 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 void setSpeed(float speed) { - mSpeed = speed; + public float getRate() { + return mRate; } /** @@ -305,7 +343,7 @@ public final class PlaybackState implements Parcelable { * */ public long getActions() { - return mCapabilities; + return mActions; } /** @@ -324,7 +362,7 @@ public final class PlaybackState implements Parcelable { * */ public void setActions(long capabilities) { - mCapabilities = capabilities; + mActions = capabilities; } /** @@ -335,6 +373,17 @@ public final class PlaybackState implements Parcelable { return mErrorMessage; } + /** + * 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 CREATOR = new Parcelable.Creator() { @Override diff --git a/media/java/android/media/session/TransportPerformer.java b/media/java/android/media/session/TransportPerformer.java index eddffd1a52ff0..187f48df1f066 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 ac7f4f37fc071..15566d6dd19ce 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; @@ -301,6 +302,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 @@ -603,7 +632,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 5dc3904189992..2e029f05231fd 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 6edcd7df6b7c7..6537d49f0fd64 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