summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
authorJean-Michel Trivi <jmtrivi@google.com>2013-09-17 22:35:07 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-09-17 22:35:08 +0000
commitf03ceff2f506133a238848c2f5db4322285cc2b7 (patch)
tree86a61d656a0a74d2c78164cdce192342a92cf6be /media/java
parent345d491b77d70942f7a4390645fae7748b418459 (diff)
parent88183e67d4628e8c8a3310af0076b6f33f955cb2 (diff)
downloadframeworks_base-f03ceff2f506133a238848c2f5db4322285cc2b7.zip
frameworks_base-f03ceff2f506133a238848c2f5db4322285cc2b7.tar.gz
frameworks_base-f03ceff2f506133a238848c2f5db4322285cc2b7.tar.bz2
Merge "Revise new public API for ratings in RemoteControlClient" into klp-dev
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/IRemoteControlClient.aidl3
-rw-r--r--media/java/android/media/MediaFocusControl.java7
-rw-r--r--media/java/android/media/MediaMetadataEditor.java462
-rw-r--r--media/java/android/media/Rating.aidl19
-rw-r--r--media/java/android/media/Rating.java274
-rw-r--r--media/java/android/media/RemoteControlClient.java253
6 files changed, 788 insertions, 230 deletions
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
index dd729b4..48079f2 100644
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ b/media/java/android/media/IRemoteControlClient.aidl
@@ -17,6 +17,7 @@ package android.media;
import android.graphics.Bitmap;
import android.media.IRemoteControlDisplay;
+import android.media.Rating;
/**
* @hide
@@ -49,5 +50,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);
+ void updateMetadata(int clientGeneration, int key, in Rating value);
} \ No newline at end of file
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index ab686e6..34dc580 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -2093,8 +2093,11 @@ public class MediaFocusControl implements OnFinished {
if ((mCurrentRcClient != null) && (mCurrentRcClientGen == genId)) {
try {
switch (key) {
- case RemoteControlClient.MetadataEditor.LONG_KEY_RATING_BY_USER:
- mCurrentRcClient.updateMetadata(genId, key, value);
+ case RemoteControlClient.MetadataEditor.RATING_KEY_BY_USER:
+ // TODO handle rating update, placeholder code here that sends
+ // an unrated percent-based rating
+ mCurrentRcClient.updateMetadata(genId, key,
+ Rating.newUnratedRating(Rating.RATING_PERCENTAGE));
break;
default:
Log.e(TAG, "unhandled metadata key " + key + " update for RCC "
diff --git a/media/java/android/media/MediaMetadataEditor.java b/media/java/android/media/MediaMetadataEditor.java
new file mode 100644
index 0000000..b601016
--- /dev/null
+++ b/media/java/android/media/MediaMetadataEditor.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+/**
+ * An abstract class for editing and storing metadata that can be published by
+ * {@link RemoteControlClient}. See the {@link RemoteControlClient#editMetadata(boolean)}
+ * method to instantiate a {@link RemoteControlClient.MetadataEditor} object.
+ */
+public abstract class MediaMetadataEditor {
+
+ private final static String TAG = "MediaMetadataEditor";
+ /**
+ * @hide
+ */
+ protected MediaMetadataEditor() {
+ }
+
+ // Public keys for metadata used by RemoteControlClient and RemoteController.
+ // Note that these keys are defined here, and not in MediaMetadataRetriever
+ // because they are not supported by the MediaMetadataRetriever features.
+ /**
+ * The metadata key for the content artwork / album art.
+ */
+ public final static int BITMAP_KEY_ARTWORK =
+ RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK;
+
+ /**
+ * The metadata key for the content's average rating, not the user's rating.
+ * The value associated with this key is a {@link Rating} instance.
+ * @see #RATING_KEY_BY_USER
+ */
+ public final static int RATING_KEY_BY_OTHERS = 101;
+
+ /**
+ * The metadata key for the content's user rating.
+ * The value associated with this key is a {@link Rating} instance.
+ * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable
+ * receiving user rating values through the
+ * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface.
+ */
+ public final static int RATING_KEY_BY_USER = 0x10000001;
+
+ /**
+ * @hide
+ * Editable key mask
+ */
+ public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF;
+
+
+ /**
+ * Applies all of the metadata changes that have been set since the MediaMetadataEditor instance
+ * was created or since {@link #clear()} was called.
+ */
+ public abstract void apply();
+
+
+ /**
+ * @hide
+ * Mask of editable keys.
+ */
+ protected long mEditableKeys;
+
+ /**
+ * @hide
+ */
+ protected boolean mMetadataChanged = false;
+
+ /**
+ * @hide
+ */
+ protected boolean mApplied = false;
+
+ /**
+ * @hide
+ */
+ protected boolean mArtworkChanged = false;
+
+ /**
+ * @hide
+ */
+ protected Bitmap mEditorArtwork;
+
+ /**
+ * @hide
+ */
+ protected Bundle mEditorMetadata;
+
+
+ /**
+ * Clears all the pending metadata changes set since the MediaMetadataEditor instance was
+ * created or since this method was last called.
+ * Note that clearing the metadata doesn't reset the editable keys
+ * (use {@link #removeEditableKeys()} instead).
+ */
+ public synchronized void clear() {
+ if (mApplied) {
+ Log.e(TAG, "Can't clear a previously applied MediaMetadataEditor");
+ return;
+ }
+ mEditorMetadata.clear();
+ mEditorArtwork = null;
+ }
+
+ /**
+ * Flags the given key as being editable.
+ * This should only be used by metadata publishers, such as {@link RemoteControlClient},
+ * which will declare the metadata field as eligible to be updated, with new values
+ * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface.
+ * @param key the type of metadata that can be edited. The supported key is
+ * {@link #RATING_KEY_BY_USER}.
+ */
+ 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 == RATING_KEY_BY_USER) {
+ mEditableKeys |= (KEY_EDITABLE_MASK & key);
+ mMetadataChanged = true;
+ } else {
+ Log.e(TAG, "Metadata key " + key + " cannot be edited");
+ }
+ }
+
+ /**
+ * Causes all metadata fields to be read-only.
+ */
+ public synchronized void removeEditableKeys() {
+ if (mApplied) {
+ Log.e(TAG, "Can't remove all editable keys of a previously applied MetadataEditor");
+ return;
+ }
+ if (mEditableKeys != 0) {
+ mEditableKeys = 0;
+ mMetadataChanged = true;
+ }
+ }
+
+ /**
+ * Retrieves the keys flagged as editable.
+ * @return null if there are no editable keys, or an array containing the keys.
+ */
+ public synchronized int[] getEditableKeys() {
+ // only one editable key supported here
+ if (mEditableKeys == RATING_KEY_BY_USER) {
+ int[] keys = { RATING_KEY_BY_USER };
+ return keys;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds textual information.
+ * Note that none of the information added after {@link #apply()} has been called,
+ * will be available to consumers of metadata stored by the MediaMetadataEditor.
+ * @param key The identifier of a the metadata field to set. Valid values are
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
+ * @param value The text for the given key, or {@code null} to signify there is no valid
+ * information for the field.
+ * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
+ * calls together.
+ */
+ public synchronized MediaMetadataEditor putString(int key, String value)
+ throws IllegalArgumentException {
+ if (mApplied) {
+ Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
+ return this;
+ }
+ if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) {
+ throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
+ }
+ mEditorMetadata.putString(String.valueOf(key), value);
+ mMetadataChanged = true;
+ return this;
+ }
+
+ /**
+ * Adds numerical information.
+ * Note that none of the information added after {@link #apply()} has been called
+ * will be available to consumers of metadata stored by the MediaMetadataEditor.
+ * @param key the identifier of a the metadata field to set. Valid values are
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
+ * expressed in milliseconds),
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
+ * @param value The long value for the given key
+ * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
+ * calls together.
+ * @throws IllegalArgumentException
+ */
+ public synchronized MediaMetadataEditor putLong(int key, long value)
+ throws IllegalArgumentException {
+ if (mApplied) {
+ Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
+ return this;
+ }
+ if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) {
+ throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
+ }
+ mEditorMetadata.putLong(String.valueOf(key), value);
+ mMetadataChanged = true;
+ return this;
+ }
+
+ /**
+ * Adds image.
+ * @param key the identifier of the bitmap to set. The only valid value is
+ * {@link #BITMAP_KEY_ARTWORK}
+ * @param bitmap The bitmap for the artwork, or null if there isn't any.
+ * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
+ * calls together.
+ * @throws IllegalArgumentException
+ * @see android.graphics.Bitmap
+ */
+ public synchronized MediaMetadataEditor putBitmap(int key, Bitmap bitmap)
+ throws IllegalArgumentException {
+ if (mApplied) {
+ Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
+ return this;
+ }
+ if (key != BITMAP_KEY_ARTWORK) {
+ throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
+ }
+ mEditorArtwork = bitmap;
+ mArtworkChanged = true;
+ return this;
+ }
+
+ /**
+ * Adds information stored as an instance.
+ * Note that none of the information added after {@link #apply()} has been called
+ * will be available to consumers of metadata stored by the MediaMetadataEditor.
+ * @param key the identifier of a the metadata field to set. Valid keys for a:
+ * <ul>
+ * <li>{@link Bitmap} object are {@link #BITMAP_KEY_ARTWORK},</li>
+ * <li>{@link String} object are the same as for {@link #putString(int, String)}</li>
+ * <li>{@link Long} object are the same as for {@link #putLong(int, long)}</li>
+ * <li>{@link Rating} object are {@link #RATING_KEY_BY_OTHERS}
+ * and {@link #RATING_KEY_BY_USER}.</li>
+ * </ul>
+ * @param obj the metadata to add.
+ * @return Returns a reference to the same MediaMetadataEditor object, so you can chain put
+ * calls together.
+ * @throws IllegalArgumentException
+ */
+ public synchronized MediaMetadataEditor putObject(int key, Object value)
+ throws IllegalArgumentException {
+ if (mApplied) {
+ Log.e(TAG, "Can't edit a previously applied MediaMetadataEditor");
+ return this;
+ }
+ switch(METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) {
+ case METADATA_TYPE_LONG:
+ if (value instanceof Long) {
+ return putLong(key, ((Long)value).longValue());
+ } else {
+ throw(new IllegalArgumentException("Not a non-null Long for key "+ key));
+ }
+ case METADATA_TYPE_STRING:
+ if ((value == null) || (value instanceof String)) {
+ return putString(key, (String) value);
+ } else {
+ throw(new IllegalArgumentException("Not a String for key "+ key));
+ }
+ case METADATA_TYPE_RATING:
+ mEditorMetadata.putParcelable(String.valueOf(key), (Parcelable)value);
+ mMetadataChanged = true;
+ break;
+ case METADATA_TYPE_BITMAP:
+ if ((value == null) || (value instanceof Bitmap)) {
+ return putBitmap(key, (Bitmap) value);
+ } else {
+ throw(new IllegalArgumentException("Not a Bitmap for key "+ key));
+ }
+ default:
+ throw(new IllegalArgumentException("Invalid key "+ key));
+ }
+ return this;
+ }
+
+
+ /**
+ * Returns the long value for the key.
+ * @param key one of the keys supported in {@link #putLong(int, long)}
+ * @param defaultValue the value returned if the key is not present
+ * @return the long value for the key, or the supplied default value if the key is not present
+ * @throws IllegalArgumentException
+ */
+ public synchronized long getLong(int key, long defaultValue)
+ throws IllegalArgumentException {
+ if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_LONG) {
+ throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
+ }
+ return mEditorMetadata.getLong(String.valueOf(key), defaultValue);
+ }
+
+ /**
+ * Returns the {@link String} value for the key.
+ * @param key one of the keys supported in {@link #putString(int, String)}
+ * @param defaultValue the value returned if the key is not present
+ * @return the {@link String} value for the key, or the supplied default value if the key is
+ * not present
+ * @throws IllegalArgumentException
+ */
+ public synchronized String getString(int key, String defaultValue)
+ throws IllegalArgumentException {
+ if (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID) != METADATA_TYPE_STRING) {
+ throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
+ }
+ return mEditorMetadata.getString(String.valueOf(key), defaultValue);
+ }
+
+ /**
+ * Returns the {@link Bitmap} value for the key.
+ * @param key the {@link #BITMAP_KEY_ARTWORK} key
+ * @param defaultValue the value returned if the key is not present
+ * @return the {@link Bitmap} value for the key, or the supplied default value if the key is
+ * not present
+ * @throws IllegalArgumentException
+ */
+ public synchronized Bitmap getBitmap(int key, Bitmap defaultValue)
+ throws IllegalArgumentException {
+ if (key != BITMAP_KEY_ARTWORK) {
+ throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
+ }
+ return (mEditorArtwork != null ? mEditorArtwork : defaultValue);
+ }
+
+ /**
+ * Returns an object representation of the value for the key
+ * @param key one of the keys supported in {@link #putObject(int, Object)}
+ * @param defaultValue the value returned if the key is not present
+ * @return the object for the key, as a {@link Long}, {@link Bitmap}, {@link String}, or
+ * {@link Rating} depending on the key value, or the supplied default value if the key is
+ * not present
+ * @throws IllegalArgumentException
+ */
+ public synchronized Object getObject(int key, Object defaultValue)
+ throws IllegalArgumentException {
+ switch (METADATA_KEYS_TYPE.get(key, METADATA_TYPE_INVALID)) {
+ case METADATA_TYPE_LONG:
+ if (mEditorMetadata.containsKey(String.valueOf(key))) {
+ return mEditorMetadata.getLong(String.valueOf(key));
+ } else {
+ return defaultValue;
+ }
+ case METADATA_TYPE_STRING:
+ if (mEditorMetadata.containsKey(String.valueOf(key))) {
+ return mEditorMetadata.getString(String.valueOf(key));
+ } else {
+ return defaultValue;
+ }
+ case METADATA_TYPE_RATING:
+ if (mEditorMetadata.containsKey(String.valueOf(key))) {
+ return mEditorMetadata.getParcelable(String.valueOf(key));
+ } else {
+ return defaultValue;
+ }
+ case METADATA_TYPE_BITMAP:
+ // only one key for Bitmap supported, value is not stored in mEditorMetadata Bundle
+ if (key == BITMAP_KEY_ARTWORK) {
+ return (mEditorArtwork != null ? mEditorArtwork : defaultValue);
+ } // else: fall through to invalid key handling
+ default:
+ throw(new IllegalArgumentException("Invalid key "+ key));
+ }
+ }
+
+
+ /**
+ * @hide
+ */
+ protected static final int METADATA_TYPE_INVALID = -1;
+ /**
+ * @hide
+ */
+ protected static final int METADATA_TYPE_LONG = 0;
+
+ /**
+ * @hide
+ */
+ protected static final int METADATA_TYPE_STRING = 1;
+
+ /**
+ * @hide
+ */
+ protected static final int METADATA_TYPE_BITMAP = 2;
+
+ /**
+ * @hide
+ */
+ protected static final int METADATA_TYPE_RATING = 3;
+
+ /**
+ * @hide
+ */
+ protected static final SparseIntArray METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new SparseIntArray(17);
+ // NOTE: if adding to the list below, make sure you increment the array initialization size
+ // keys with long values
+ METADATA_KEYS_TYPE.put(
+ MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_TYPE_LONG);
+ // keys with String values
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(
+ MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(
+ MediaMetadataRetriever.METADATA_KEY_COMPILATION, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_TYPE_STRING);
+ METADATA_KEYS_TYPE.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_TYPE_STRING);
+ // keys with Bitmap values
+ METADATA_KEYS_TYPE.put(BITMAP_KEY_ARTWORK, METADATA_TYPE_BITMAP);
+ // keys with Rating values
+ METADATA_KEYS_TYPE.put(RATING_KEY_BY_OTHERS, METADATA_TYPE_RATING);
+ METADATA_KEYS_TYPE.put(RATING_KEY_BY_USER, METADATA_TYPE_RATING);
+ }
+}
diff --git a/media/java/android/media/Rating.aidl b/media/java/android/media/Rating.aidl
new file mode 100644
index 0000000..1dc336a
--- /dev/null
+++ b/media/java/android/media/Rating.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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;
+
+parcelable Rating;
diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java
new file mode 100644
index 0000000..48443ff
--- /dev/null
+++ b/media/java/android/media/Rating.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class to encapsulate rating information used as content metadata.
+ * A rating is defined by its rating style (see {@link #RATING_HEART},
+ * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
+ * be defined as "unrated"), both of which are defined when the rating instance is constructed
+ * through one of the factory methods.
+ */
+public final class Rating implements Parcelable {
+
+ private final static String TAG = "Rating";
+
+ /**
+ * 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).
+ */
+ public final static int RATING_HEART = 1;
+
+ /**
+ * A rating style for "thumb up" vs "thumb down".
+ */
+ public final static int RATING_THUMB_UP_DOWN = 2;
+
+ /**
+ * A rating style with 0 to 3 stars.
+ */
+ public final static int RATING_3_STARS = 3;
+
+ /**
+ * A rating style with 0 to 4 stars.
+ */
+ public final static int RATING_4_STARS = 4;
+
+ /**
+ * A rating style with 0 to 5 stars.
+ */
+ public final static int RATING_5_STARS = 5;
+
+ /**
+ * A rating style expressed as a percentage.
+ */
+ public final static int RATING_PERCENTAGE = 6;
+
+ private final static float RATING_NOT_RATED = -1.0f;
+
+ private final int mRatingStyle;
+
+ private final float mRatingValue;
+
+ private Rating(int ratingStyle, float rating) {
+ mRatingStyle = ratingStyle;
+ mRatingValue = rating;
+ }
+
+ @Override
+ public int describeContents() {
+ return mRatingStyle;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRatingStyle);
+ dest.writeFloat(mRatingValue);
+ }
+
+ public static final Parcelable.Creator<Rating> CREATOR
+ = new Parcelable.Creator<Rating>() {
+ /**
+ * Rebuilds a Rating previously stored with writeToParcel().
+ * @param p Parcel object to read the Rating from
+ * @return a new Rating created from the data in the parcel
+ */
+ public Rating createFromParcel(Parcel p) {
+ return new Rating(p.readInt(), p.readFloat());
+ }
+ public Rating[] newArray(int size) {
+ return new Rating[size];
+ }
+ };
+
+ /**
+ * Return a Rating instance with no rating.
+ * Create and return a new Rating instance with no rating known for the given
+ * rating style.
+ * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ * @return null if an invalid rating style is passed, a new Rating instance otherwise.
+ */
+ public static Rating newUnratedRating(int ratingStyle) {
+ switch(ratingStyle) {
+ case RATING_HEART:
+ case RATING_THUMB_UP_DOWN:
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ case RATING_PERCENTAGE:
+ return new Rating(ratingStyle, RATING_NOT_RATED);
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Return a Rating instance with a heart-based rating.
+ * Create and return a new Rating instance with a rating style of {@link #RATING_HEART},
+ * and a heart-based rating.
+ * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
+ * @return a new Rating instance.
+ */
+ public static Rating newHeartRating(boolean hasHeart) {
+ return new Rating(RATING_HEART, hasHeart ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating instance with a thumb-based rating.
+ * Create and return a new Rating instance with a {@link #RATING_THUMB_UP_DOWN}
+ * rating style, and a "thumb up" or "thumb down" rating.
+ * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
+ * @return a new Rating instance.
+ */
+ public static Rating newThumbRating(boolean thumbIsUp) {
+ return new Rating(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
+ }
+
+ /**
+ * Return a Rating instance with a star-based rating.
+ * Create and return a new Rating instance with one of the star-base rating styles
+ * and the given integer or fractional number of stars. Non integer values can for instance
+ * be used to represent an average rating value, which might not be an integer number of stars.
+ * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS}.
+ * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
+ * the rating style.
+ * @return null if the rating style is invalid, or the rating is out of range,
+ * a new Rating instance otherwise.
+ */
+ public static Rating newStarRating(int starRatingStyle, float starRating) {
+ float maxRating = -1.0f;
+ switch(starRatingStyle) {
+ case RATING_3_STARS:
+ maxRating = 3.0f;
+ break;
+ case RATING_4_STARS:
+ maxRating = 4.0f;
+ break;
+ case RATING_5_STARS:
+ maxRating = 5.0f;
+ break;
+ default:
+ Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
+ return null;
+ }
+ if ((starRating < 0.0f) || (starRating > maxRating)) {
+ Log.e(TAG, "Trying to set out of range star-based rating");
+ return null;
+ }
+ return new Rating(starRatingStyle, starRating);
+ }
+
+ /**
+ * Return a Rating instance with a percentage-based rating.
+ * Create and return a new Rating instance with a {@link #RATING_PERCENTAGE}
+ * rating style, and a rating of the given percentage.
+ * @param percent the value of the rating
+ * @return null if the rating is out of range, a new Rating instance otherwise.
+ */
+ public static Rating newPercentageRating(float percent) {
+ if ((percent < 0.0f) || (percent > 100.0f)) {
+ Log.e(TAG, "Invalid percentage-based rating value");
+ return null;
+ } else {
+ return new Rating(RATING_PERCENTAGE, percent);
+ }
+ }
+
+ /**
+ * Return whether there is a rating value available.
+ * @return true if the instance was not created with {@link #newUnratedRating(int)}.
+ */
+ public boolean isRated() {
+ return mRatingValue >= 0.0f;
+ }
+
+ /**
+ * Return the rating style.
+ * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+ * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+ * or {@link #RATING_PERCENTAGE}.
+ */
+ public int getRatingStyle() {
+ return mRatingStyle;
+ }
+
+ /**
+ * Return whether the rating is "heart selected".
+ * @return true if the rating is "heart selected", false if the rating is "heart unselected",
+ * if the rating style is not {@link #RATING_HEART} or if it is unrated.
+ */
+ public boolean hasHeart() {
+ if (mRatingStyle != RATING_HEART) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return whether the rating is "thumb up".
+ * @return true if the rating is "thumb up", false if the rating is "thumb down",
+ * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
+ */
+ public boolean isThumbUp() {
+ if (mRatingStyle != RATING_THUMB_UP_DOWN) {
+ return false;
+ } else {
+ return (mRatingValue == 1.0f);
+ }
+ }
+
+ /**
+ * Return the star-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not star-based, or if it is unrated.
+ */
+ public float getStarRating() {
+ switch (mRatingStyle) {
+ case RATING_3_STARS:
+ case RATING_4_STARS:
+ case RATING_5_STARS:
+ if (isRated()) {
+ return mRatingValue;
+ }
+ default:
+ return -1.0f;
+ }
+ }
+
+ /**
+ * Return the percentage-based rating value.
+ * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+ * not percentage-based, or if it is unrated.
+ */
+ public float getPercentRating() {
+ if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
+ return -1.0f;
+ } else {
+ return mRatingValue;
+ }
+ }
+} \ No newline at end of file
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 58f5d55..f8faf3a 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -30,6 +30,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -385,27 +386,6 @@ public class RemoteControlClient
mEventHandler = new EventHandler(this, looper);
}
- private static final int[] METADATA_KEYS_TYPE_STRING = {
- MediaMetadataRetriever.METADATA_KEY_ALBUM,
- MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
- MediaMetadataRetriever.METADATA_KEY_TITLE,
- MediaMetadataRetriever.METADATA_KEY_ARTIST,
- MediaMetadataRetriever.METADATA_KEY_AUTHOR,
- MediaMetadataRetriever.METADATA_KEY_COMPILATION,
- MediaMetadataRetriever.METADATA_KEY_COMPOSER,
- MediaMetadataRetriever.METADATA_KEY_DATE,
- MediaMetadataRetriever.METADATA_KEY_GENRE,
- MediaMetadataRetriever.METADATA_KEY_TITLE,
- MediaMetadataRetriever.METADATA_KEY_WRITER };
- 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_YEAR,
- 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.
* Use {@link RemoteControlClient#editMetadata(boolean)} to create an instance of an editor,
@@ -414,28 +394,7 @@ public class RemoteControlClient
* for the associated client. Once the metadata has been "applied", you cannot reuse this
* instance of the MetadataEditor.
*/
- public class MetadataEditor {
- /**
- * Mask of editable keys.
- */
- private long mEditableKeys;
- /**
- * @hide
- */
- protected boolean mMetadataChanged;
- /**
- * @hide
- */
- protected boolean mArtworkChanged;
- /**
- * @hide
- */
- protected Bitmap mEditorArtwork;
- /**
- * @hide
- */
- protected Bundle mEditorMetadata;
- private boolean mApplied = false;
+ public class MetadataEditor extends MediaMetadataEditor {
// only use RemoteControlClient.editMetadata() to get a MetadataEditor instance
private MetadataEditor() { }
@@ -450,73 +409,10 @@ public class RemoteControlClient
* The metadata key for the content artwork / album art.
*/
public final static int BITMAP_KEY_ARTWORK = 100;
- /**
- * 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;
- /**
- * 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 51 to 100 means "heart selected",</li>
- * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 50 means "thumb down",
- * 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 0 maps to 0 stars, 1 to 20 maps to 1 star,
- * 21 to 40 maps to 2 stars, etc.</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;
- /**
- * The metadata key for the content's user rating.
- * 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}.
- * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable
- * receiving user rating values through the
- * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface.
- */
- public final static int LONG_KEY_RATING_BY_USER = 0x10000001;
-
- /**
- * 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;
- /**
- * A rating style for "thumb up" vs "thumb down".
- * @see #LONG_KEY_RATING_TYPE
- */
- public final static long RATING_THUMB_UP_DOWN = -2;
- /**
- * 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
+ * TODO(jmtrivi) have lockscreen move to the new key name and remove
*/
public final static int METADATA_KEY_ARTWORK = BITMAP_KEY_ARTWORK;
@@ -543,15 +439,7 @@ public class RemoteControlClient
*/
public synchronized MetadataEditor putString(int key, String value)
throws IllegalArgumentException {
- if (mApplied) {
- Log.e(TAG, "Can't edit a previously applied MetadataEditor");
- return this;
- }
- if (!validTypeForKey(key, METADATA_KEYS_TYPE_STRING)) {
- throw(new IllegalArgumentException("Invalid type 'String' for key "+ key));
- }
- mEditorMetadata.putString(String.valueOf(key), value);
- mMetadataChanged = true;
+ super.putString(key, value);
return this;
}
@@ -564,9 +452,7 @@ public class RemoteControlClient
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
* expressed in milliseconds),
- * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR},
- * {@link #LONG_KEY_RATING_BY_OTHERS}, {@link #LONG_KEY_RATING_BY_USER},
- * {@link #LONG_KEY_RATING_TYPE}.
+ * {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
* @param value The long value for the given key
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
@@ -574,15 +460,7 @@ public class RemoteControlClient
*/
public synchronized MetadataEditor putLong(int key, long value)
throws IllegalArgumentException {
- if (mApplied) {
- Log.e(TAG, "Can't edit a previously applied MetadataEditor");
- return this;
- }
- if (!validTypeForKey(key, METADATA_KEYS_TYPE_LONG)) {
- throw(new IllegalArgumentException("Invalid type 'long' for key "+ key));
- }
- mEditorMetadata.putLong(String.valueOf(key), value);
- mMetadataChanged = true;
+ super.putLong(key, value);
return this;
}
@@ -596,69 +474,22 @@ public class RemoteControlClient
* @throws IllegalArgumentException
* @see android.graphics.Bitmap
*/
+ @Override
public synchronized MetadataEditor putBitmap(int key, Bitmap bitmap)
throws IllegalArgumentException {
- if (mApplied) {
- Log.e(TAG, "Can't edit a previously applied MetadataEditor");
- return this;
- }
- if (key != BITMAP_KEY_ARTWORK) {
- throw(new IllegalArgumentException("Invalid type 'Bitmap' for key "+ key));
- }
- mEditorArtwork = bitmap;
- mArtworkChanged = true;
+ super.putBitmap(key, bitmap);
return this;
}
/**
- * Clears all the metadata that has been set since the MetadataEditor instance was
- * created with {@link RemoteControlClient#editMetadata(boolean)}.
+ * Clears all the metadata that has been set since the MetadataEditor instance was created
+ * (with {@link RemoteControlClient#editMetadata(boolean)}).
* Note that clearing the metadata doesn't reset the editable keys
- * (use {@link #clearEditableKeys()} instead).
+ * (use {@link MediaMetadataEditor#removeEditableKeys()} instead).
*/
+ @Override
public synchronized void clear() {
- if (mApplied) {
- Log.e(TAG, "Can't clear a previously applied MetadataEditor");
- return;
- }
- mEditorMetadata.clear();
- mEditorArtwork = null;
- }
-
- /**
- * Flag the given key as being editable.
- * This will declare the metadata field as eligible to be updated, with new values
- * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface.
- * @param key the type of metadata that can be edited. The supported key is
- * {@link #LONG_KEY_RATING_BY_USER}.
- */
- 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");
- }
- }
-
- /**
- * Causes all metadata fields to be read-only.
- */
- 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;
- }
+ super.clear();
}
/**
@@ -881,30 +712,17 @@ public class RemoteControlClient
/**
* Interface definition for a callback to be invoked when one of the metadata values has
* been updated.
+ * Implement this interface to receive metadata updates after registering your listener
+ * through {@link RemoteControlClient#setMetadataUpdateListener(OnMetadataUpdateListener)}.
*/
public interface OnMetadataUpdateListener {
/**
* Called on the implementer to notify that the metadata field for the given key has
- * been updated to the new value of type <code>long</long>.
- * @param key the identifier of the updated metadata field of type <code>long</long>.
- * @param newValue the new <code>long</long> value for the key
+ * been updated to the new value.
+ * @param key the identifier of the updated metadata field.
+ * @param newValue the Object storing the new value for the key.
*/
- void onMetadataUpdateLong(int key, long newValue);
- /**
- * Called on the implementer to notify that the metadata field for the given key has
- * been updated to the new <code>String</long>.
- * @param key the identifier of the updated metadata field of type <code>String</long>.
- * @param newValue the new <code>String</long> value for the key
- */
- void onMetadataUpdateString(int key, String newValue);
- /**
- * Called on the implementer to notify that the metadata field for the given key has
- * been updated to the new {@link android.graphics.Bitmap}.
- * @param key the identifier of the updated metadata field of type
- * {@link android.graphics.Bitmap}.
- * @param newValue the new {@link android.graphics.Bitmap} for the key
- */
- void onMetadataUpdateBitmap(int key, Bitmap newValue);
+ public abstract void onMetadataUpdate(int key, Object newValue);
}
/**
@@ -1338,12 +1156,11 @@ public class RemoteControlClient
}
}
- public void updateMetadata(int generationId, int key, long value) {
+ public void updateMetadata(int generationId, int key, Rating 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)));
+ MSG_UPDATE_METADATA, generationId /* arg1 */, key /* arg2*/, value));
}
}
};
@@ -1389,7 +1206,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 final static int MSG_UPDATE_METADATA = 13;
private class EventHandler extends Handler {
public EventHandler(RemoteControlClient rcc, Looper looper) {
@@ -1443,8 +1260,8 @@ 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());
+ case MSG_UPDATE_METADATA:
+ onUpdateMetadata(msg.arg1, msg.arg2, msg.obj);
break;
default:
Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
@@ -1725,10 +1542,10 @@ public class RemoteControlClient
}
}
- private void onUpdateMetadata(int generationId, int key, long value) {
+ private void onUpdateMetadata(int generationId, int key, Object value) {
synchronized (mCacheLock) {
if ((mCurrentClientGenId == generationId) && (mMetadataUpdateListener != null)) {
- mMetadataUpdateListener.onMetadataUpdateLong(key, value);
+ mMetadataUpdateListener.onMetadataUpdate(key, value);
}
}
}
@@ -1771,24 +1588,6 @@ public class RemoteControlClient
return bitmap;
}
- /**
- * Fast routine to go through an array of allowed keys and return whether the key is part
- * of that array
- * @param key the key value
- * @param validKeys the array of valid keys for a given type
- * @return true if the key is part of the array, false otherwise
- */
- private static boolean validTypeForKey(int key, int[] validKeys) {
- try {
- for (int i = 0 ; ; i++) {
- if (key == validKeys[i]) {
- return true;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- return false;
- }
- }
/**
* Returns whether, for the given playback state, the playback position is expected to