summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2013-08-20 09:04:56 -0700
committerJean-Michel Trivi <jmtrivi@google.com>2013-08-22 09:29:19 -0700
commitf823fc4dba2df5cf5f00e13361f2db93c81f6961 (patch)
treef8e3926409dcda0ef737407b6d1c730a391bc6b3
parentcb13399c1b3a0067a6b5b81cd3dc3833a5e9dcd9 (diff)
downloadframeworks_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.java18
-rw-r--r--media/java/android/media/AudioService.java6
-rw-r--r--media/java/android/media/IAudioService.aidl8
-rw-r--r--media/java/android/media/IRemoteControlClient.aidl1
-rw-r--r--media/java/android/media/MediaFocusControl.java40
-rw-r--r--media/java/android/media/RemoteControlClient.java196
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