diff options
| author | Jean-Michel Trivi <jmtrivi@google.com> | 2013-08-20 09:04:56 -0700 |
|---|---|---|
| committer | Jean-Michel Trivi <jmtrivi@google.com> | 2013-08-22 09:29:19 -0700 |
| commit | f823fc4dba2df5cf5f00e13361f2db93c81f6961 (patch) | |
| tree | f8e3926409dcda0ef737407b6d1c730a391bc6b3 | |
| parent | cb13399c1b3a0067a6b5b81cd3dc3833a5e9dcd9 (diff) | |
| download | frameworks_base-f823fc4dba2df5cf5f00e13361f2db93c81f6961.zip frameworks_base-f823fc4dba2df5cf5f00e13361f2db93c81f6961.tar.gz frameworks_base-f823fc4dba2df5cf5f00e13361f2db93c81f6961.tar.bz2 | |
Ratings for RemoteControl
Add support for metadata of a RemoteControlClient that can be
updated:
- methods to control which keys can be edited,
- interface for an application to receive new values for a key.
Add definitions for ratings.
A rating is:
- a value between 0 and 100
- or a value indicating there is no rating
For a same piece of content, a rating can come from:
- the user
- "others" (i.e. not the user), to provide an average rating
Rating styles are:
- heart (a toggle)
- thumb up / down
- stars (with a configurable maximum number of stars)
Rating by user is editable metadata.
Bug 8440498
Change-Id: I1d45972f9ace4cb505ee0757e917f1d5dedd264e
| -rw-r--r-- | media/java/android/media/AudioManager.java | 18 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 6 | ||||
| -rw-r--r-- | media/java/android/media/IAudioService.aidl | 8 | ||||
| -rw-r--r-- | media/java/android/media/IRemoteControlClient.aidl | 1 | ||||
| -rw-r--r-- | media/java/android/media/MediaFocusControl.java | 40 | ||||
| -rw-r--r-- | media/java/android/media/RemoteControlClient.java | 196 |
6 files changed, 267 insertions, 2 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ef02cfd..1b9043a 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2373,6 +2373,24 @@ public class AudioManager { } /** + * @hide + * Notify the user of a RemoteControlClient that it should update its metadata + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param key the metadata key for which a new value exists + * @param value the new metadata value + */ + public void updateRemoteControlClientMetadata(int generationId, int key, long value) { + IAudioService service = getService(); + try { + service.updateRemoteControlClientMetadata(generationId, key, value); + } catch (RemoteException e) { + Log.e(TAG, "Dead object in updateRemoteControlClientMetadata("+ generationId + ", " + + key +", " + value + ")", e); + } + } + + /** * @hide * Reload audio settings. This method is called by Settings backup * agent when audio settings are restored and causes the AudioService diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 470c571..7269f0c 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -4203,7 +4203,7 @@ public class AudioService extends IAudioService.Stub { public void unregisterMediaButtonIntent(PendingIntent pi) { mMediaFocusControl.unregisterMediaButtonIntent(pi); } - + public int registerRemoteControlClient(PendingIntent mediaIntent, IRemoteControlClient rcClient, String callingPckg) { return mMediaFocusControl.registerRemoteControlClient(mediaIntent, rcClient, callingPckg); @@ -4218,6 +4218,10 @@ public class AudioService extends IAudioService.Stub { mMediaFocusControl.setRemoteControlClientPlaybackPosition(generationId, timeMs); } + public void updateRemoteControlClientMetadata(int generationId, int key, long value) { + mMediaFocusControl.updateRemoteControlClientMetadata(generationId, key, value); + } + public void registerRemoteVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { mMediaFocusControl.registerRemoteVolumeObserverForRcc(rccId, rvo); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 903927b..c7a749a 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -181,6 +181,14 @@ interface IAudioService { * @param timeMs the time in ms to seek to, must be positive. */ void setRemoteControlClientPlaybackPosition(int generationId, long timeMs); + /** + * Notify the user of a RemoteControlClient that it should update its metadata + * @param generationId the RemoteControlClient generation counter for which this request is + * issued. Requests for an older generation than current one will be ignored. + * @param key the metadata key for which a new value exists + * @param value the new metadata value + */ + void updateRemoteControlClientMetadata(int generationId, int key, long value); /** * Do not use directly, use instead diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl index 2236129..dd729b4 100644 --- a/media/java/android/media/IRemoteControlClient.aidl +++ b/media/java/android/media/IRemoteControlClient.aidl @@ -49,4 +49,5 @@ oneway interface IRemoteControlClient void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync); void seekTo(int clientGeneration, long timeMs); + void updateMetadata(int clientGeneration, int key, long value); }
\ No newline at end of file diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java index 60a84a6..ab686e6 100644 --- a/media/java/android/media/MediaFocusControl.java +++ b/media/java/android/media/MediaFocusControl.java @@ -138,6 +138,7 @@ public class MediaFocusControl implements OnFinished { private static final int MSG_PROMOTE_RCC = 6; private static final int MSG_RCC_NEW_PLAYBACK_STATE = 7; private static final int MSG_RCC_SEEK_REQUEST = 8; + private static final int MSG_RCC_UPDATE_METADATA_LONG = 9; // sendMsg() flags /** If the msg is already queued, replace it with this one. */ @@ -188,18 +189,27 @@ public class MediaFocusControl implements OnFinished { onNewPlaybackInfoForRcc(msg.arg1 /* rccId */, msg.arg2 /* key */, ((Integer)msg.obj).intValue() /* value */); break; + case MSG_RCC_NEW_VOLUME_OBS: onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, (IRemoteVolumeObserver)msg.obj /* rvo */); break; + case MSG_RCC_NEW_PLAYBACK_STATE: onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */, (RccPlaybackState)msg.obj /* newState */); break; + case MSG_RCC_SEEK_REQUEST: onSetRemoteControlClientPlaybackPosition( msg.arg1 /* generationId */, ((Long)msg.obj).longValue() /* timeMs */); + break; + + case MSG_RCC_UPDATE_METADATA_LONG: + onUpdateRemoteControlClientMetadataLong(msg.arg1 /*genId*/, msg.arg2 /*key*/, + ((Long)msg.obj).longValue() /* value */); + break; case MSG_PROMOTE_RCC: onPromoteRcc(msg.arg1); @@ -2070,6 +2080,36 @@ public class MediaFocusControl implements OnFinished { } } + protected void updateRemoteControlClientMetadata(int genId, int key, long value) { + sendMsg(mEventHandler, MSG_RCC_UPDATE_METADATA_LONG, SENDMSG_QUEUE, + genId /* arg1 */, key /* arg2 */, Long.valueOf(value) /* obj */, 0 /* delay */); + } + + private void onUpdateRemoteControlClientMetadataLong(int genId, int key, long value) { + if(DEBUG_RC) Log.d(TAG, "onUpdateRemoteControlClientMetadataLong(genId=" + genId + + ", what=" + key + ",val=" + value + ")"); + synchronized(mRCStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) { + try { + switch (key) { + case RemoteControlClient.MetadataEditor.LONG_KEY_RATING_BY_USER: + mCurrentRcClient.updateMetadata(genId, key, value); + break; + default: + Log.e(TAG, "unhandled metadata key " + key + " update for RCC " + + genId); + break; + } + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead", e); + mCurrentRcClient = null; + } + } + } + } + } + protected void setPlaybackInfoForRcc(int rccId, int what, int value) { sendMsg(mEventHandler, MSG_RCC_NEW_PLAYBACK_INFO, SENDMSG_QUEUE, rccId /* arg1 */, what /* arg2 */, Integer.valueOf(value) /* obj */, 0 /* delay */); diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 7379438..2c211cc 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -293,6 +293,18 @@ public class RemoteControlClient * @see #setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener) */ public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8; + /** + * @hide + * CANDIDATE FOR PUBLIC API + * Flag indicating a RemoteControlClient supports ratings. + * This flag must be set in order for components that display the RemoteControlClient + * information, to display ratings information, and, if ratings are declared editable + * (by calling {@link MetadataEditor#addEditableKey(int)} with the + * {@link MetadataEditor#LONG_KEY_RATING_BY_USER} key), it will enable the user to rate + * the media. + * @see #setTransportControlFlags(int) + */ + public final static int FLAG_KEY_MEDIA_RATING = 1 << 9; /** * @hide @@ -389,7 +401,10 @@ public class RemoteControlClient private static final int[] METADATA_KEYS_TYPE_LONG = { MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, - MediaMetadataRetriever.METADATA_KEY_DURATION }; + MediaMetadataRetriever.METADATA_KEY_DURATION, + MetadataEditor.LONG_KEY_RATING_TYPE, + MetadataEditor.LONG_KEY_RATING_BY_OTHERS, + MetadataEditor.LONG_KEY_RATING_BY_USER}; /** * Class used to modify metadata in a {@link RemoteControlClient} object. @@ -401,6 +416,10 @@ public class RemoteControlClient */ public class MetadataEditor { /** + * Mask of editable keys. + */ + private long mEditableKeys; + /** * @hide */ protected boolean mMetadataChanged; @@ -433,6 +452,78 @@ public class RemoteControlClient public final static int BITMAP_KEY_ARTWORK = 100; /** * @hide + * CANDIDATE FOR PUBLIC API + * The metadata key qualifying the content rating. + * The value associated with this key may be: {@link #RATING_HEART}, + * {@link #RATING_THUMB_UP_DOWN}, or a non-null positive integer expressing a maximum + * number of "stars" for the rating, for which a typical value is 3 or 5. + */ + public final static int LONG_KEY_RATING_TYPE = 101; + /** + * @hide + * CANDIDATE FOR PUBLIC API + * The metadata key for the content's average rating, not the user's rating. + * The value associated with this key may be: an integer value between 0 and 100, + * or {@link #RATING_NOT_RATED} to express that no average rating is available. + * <p></p> + * Note that a rating value up to 100 is not incompatible with a rating type using up + * to 5 stars for instance, as the average may be an non-integer number of stars. + * <p></p> + * When the rating type is: + * <ul> + * <li>{@link #RATING_HEART}, a rating of 50 to 100 means "heart selected",</li> + * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 49 means "thumb down", 50 means + * both "thumb up" and "thumb down" selected, 51 to 100 means "thumb up"</li> + * <li>a non-null positive integer, the rating value is mapped to the number of stars, e.g. + * with a maximum of 5 stars, a rating of 21 to 40 maps to 2 stars.</li> + * </ul> + * @see #LONG_KEY_RATING_BY_USER + */ + public final static int LONG_KEY_RATING_BY_OTHERS = 102; + + // editable keys + /** + * @hide + * Editable key mask + */ + public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF; + /** + * @hide + * CANDIDATE FOR PUBLIC API + * The metadata key for the content's rating by the user. + * The value associated with this key may be: an integer value between 0 and 100, + * or {@link #RATING_NOT_RATED} to express that the user hasn't rated this content. + * Rules for the interpretation of the rating value according to the rating style are + * the same as for {@link #LONG_KEY_RATING_BY_OTHERS} + */ + public final static int LONG_KEY_RATING_BY_USER = 0x10000001; + + /** + * @hide + * CANDIDATE FOR PUBLIC API + * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to + * indicate the content referred to is a favorite (or not). + * @see #LONG_KEY_RATING_TYPE + */ + public final static long RATING_HEART = -1; + /** + * @hide + * CANDIDATE FOR PUBLIC API + * A rating style for "thumb up" vs "thumb down". + * @see #LONG_KEY_RATING_TYPE + */ + public final static long RATING_THUMB_UP_DOWN = -2; + /** + * @hide + * CANDIDATE FOR PUBLIC API + * A rating value indicating no rating is available. + * @see #LONG_KEY_RATING_BY_OTHERS + * @see #LONG_KEY_RATING_BY_USER + */ + public final static long RATING_NOT_RATED = -101; + + /** + * @hide * TODO(jmtrivi) have lockscreen and music move to the new key name */ public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK; @@ -529,6 +620,7 @@ public class RemoteControlClient * Clears all the metadata that has been set since the MetadataEditor instance was * created with {@link RemoteControlClient#editMetadata(boolean)}. */ + // TODO add in javadoc that this doesn't call clearEditableKeys() public synchronized void clear() { if (mApplied) { Log.e(TAG, "Can't clear a previously applied MetadataEditor"); @@ -539,6 +631,40 @@ public class RemoteControlClient } /** + * @hide + * CANDIDATE FOR PUBLIC API + */ + public synchronized void addEditableKey(int key) { + if (mApplied) { + Log.e(TAG, "Can't change editable keys of a previously applied MetadataEditor"); + return; + } + // only one editable key at the moment, so we're not wasting memory on an array + // of editable keys to check the validity of the key, just hardcode the supported key. + if (key == MetadataEditor.LONG_KEY_RATING_BY_USER) { + mEditableKeys |= (MetadataEditor.KEY_EDITABLE_MASK & key); + mMetadataChanged = true; + } else { + Log.e(TAG, "Metadata key " + key + " cannot be edited"); + } + } + + /** + * @hide + * CANDIDATE FOR PUBLIC API + */ + public synchronized void clearEditableKeys() { + if (mApplied) { + Log.e(TAG, "Can't clear editable keys of a previously applied MetadataEditor"); + return; + } + if (mEditableKeys != 0) { + mEditableKeys = 0; + mMetadataChanged = true; + } + } + + /** * Associates all the metadata that has been set since the MetadataEditor instance was * created with {@link RemoteControlClient#editMetadata(boolean)}, or since * {@link #clear()} was called, with the RemoteControlClient. Once "applied", @@ -552,6 +678,8 @@ public class RemoteControlClient synchronized(mCacheLock) { // assign the edited data mMetadata = new Bundle(mEditorMetadata); + // add the information about editable keys + mMetadata.putLong(String.valueOf(KEY_EDITABLE_MASK), mEditableKeys); if ((mOriginalArtwork != null) && (!mOriginalArtwork.equals(mEditorArtwork))) { mOriginalArtwork.recycle(); } @@ -585,6 +713,7 @@ public class RemoteControlClient editor.mEditorArtwork = null; editor.mMetadataChanged = true; editor.mArtworkChanged = true; + editor.mEditableKeys = 0; } else { editor.mEditorMetadata = new Bundle(mMetadata); editor.mEditorArtwork = mOriginalArtwork; @@ -752,6 +881,45 @@ public class RemoteControlClient } /** + * @hide + * CANDIDATE FOR PUBLIC API + * TODO ADD DESCRIPTION + */ + public interface OnMetadataUpdateListener { + /** + * TODO ADD DESCRIPTION + * @param key + * @param newValue + */ + void onMetadataUpdateLong(int key, long newValue); + /** + * TODO ADD DESCRIPTION + * @param key + * @param newValue + */ + void onMetadataUpdateString(int key, String newValue); + /** + * TODO ADD DESCRIPTION + * @param key + * @param newValue + */ + void onMetadataUpdateBitmap(int key, Bitmap newValue); + } + + /** + * @hide + * CANDIDATE FOR PUBLIC API + * TODO ADD DESCRIPTION + * @param l + */ + public void setMetadataUpdateListener(OnMetadataUpdateListener l) { + synchronized(mCacheLock) { + mMetadataUpdateListener = l; + } + } + + + /** * Interface definition for a callback to be invoked when the media playback position is * requested to be updated. * @see RemoteControlClient#FLAG_KEY_MEDIA_POSITION_UPDATE @@ -1023,6 +1191,11 @@ public class RemoteControlClient */ private OnGetPlaybackPositionListener mPositionProvider; /** + * Listener registered by user of RemoteControlClient to receive edit changes to metadata + * it exposes. + */ + private OnMetadataUpdateListener mMetadataUpdateListener; + /** * The current remote control client generation ID across the system, as known by this object */ private int mCurrentClientGenId = -1; @@ -1163,6 +1336,15 @@ public class RemoteControlClient new Long(timeMs))); } } + + public void updateMetadata(int generationId, int key, long value) { + // only post messages, we can't block here + if (mEventHandler != null) { + mEventHandler.sendMessage(mEventHandler.obtainMessage( + MSG_UPDATE_METADATA_LONG, generationId /* arg1 */, key /* arg2*/, + new Long(value))); + } + } }; /** @@ -1206,6 +1388,7 @@ public class RemoteControlClient private final static int MSG_SEEK_TO = 10; private final static int MSG_POSITION_DRIFT_CHECK = 11; private final static int MSG_DISPLAY_WANTS_POS_SYNC = 12; + private final static int MSG_UPDATE_METADATA_LONG = 13; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1259,6 +1442,9 @@ public class RemoteControlClient case MSG_DISPLAY_WANTS_POS_SYNC: onDisplayWantsSync((IRemoteControlDisplay)msg.obj, msg.arg1 == 1); break; + case MSG_UPDATE_METADATA_LONG: + onUpdateMetadata(msg.arg1, msg.arg2, ((Long)msg.obj).longValue()); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1538,6 +1724,14 @@ public class RemoteControlClient } } + private void onUpdateMetadata(int generationId, int key, long value) { + synchronized (mCacheLock) { + if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) { + mMetadataUpdateListener.onMetadataUpdateLong(key, value); + } + } + } + //=========================================================== // Internal utilities |
