diff options
author | Jean-Michel Trivi <jmtrivi@google.com> | 2013-09-17 22:35:07 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-09-17 22:35:08 +0000 |
commit | f03ceff2f506133a238848c2f5db4322285cc2b7 (patch) | |
tree | 86a61d656a0a74d2c78164cdce192342a92cf6be /media/java | |
parent | 345d491b77d70942f7a4390645fae7748b418459 (diff) | |
parent | 88183e67d4628e8c8a3310af0076b6f33f955cb2 (diff) | |
download | frameworks_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.aidl | 3 | ||||
-rw-r--r-- | media/java/android/media/MediaFocusControl.java | 7 | ||||
-rw-r--r-- | media/java/android/media/MediaMetadataEditor.java | 462 | ||||
-rw-r--r-- | media/java/android/media/Rating.aidl | 19 | ||||
-rw-r--r-- | media/java/android/media/Rating.java | 274 | ||||
-rw-r--r-- | media/java/android/media/RemoteControlClient.java | 253 |
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 |