/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.browse.MediaBrowser; import android.media.session.MediaController; import android.media.session.MediaSession; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.Set; /** * Contains metadata about an item, such as the title, artist, etc. */ public final class MediaMetadata implements Parcelable { private static final String TAG = "MediaMetadata"; /** * @hide */ @StringDef(prefix = { "METADATA_KEY_" }, value = { METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR, METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION, METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI, METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE, METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI, METADATA_KEY_MEDIA_ID, METADATA_KEY_MEDIA_URI, }) @Retention(RetentionPolicy.SOURCE) public @interface TextKey {} /** * @hide */ @StringDef(prefix = { "METADATA_KEY_" }, value = { METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER, METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BT_FOLDER_TYPE, }) @Retention(RetentionPolicy.SOURCE) public @interface LongKey {} /** * @hide */ @StringDef(prefix = { "METADATA_KEY_" }, value = { METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON, }) @Retention(RetentionPolicy.SOURCE) public @interface BitmapKey {} /** * @hide */ @StringDef(prefix = { "METADATA_KEY_" }, value = { METADATA_KEY_USER_RATING, METADATA_KEY_RATING, }) @Retention(RetentionPolicy.SOURCE) public @interface RatingKey {} /** * The title of the media. */ public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE"; /** * The artist of the media. */ public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST"; /** * The duration of the media in ms. A negative duration indicates that the * duration is unknown (or infinite). */ public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION"; /** * The album title for the media. */ public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM"; /** * The author of the media. */ public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR"; /** * The writer of the media. */ public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER"; /** * The composer of the media. */ public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER"; /** * The compilation status of the media. */ public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION"; /** * The date the media was created or published. The format is unspecified * but RFC 3339 is recommended. */ public static final String METADATA_KEY_DATE = "android.media.metadata.DATE"; /** * The year the media was created or published as a long. */ public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR"; /** * The genre of the media. */ public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE"; /** * The track number for the media. */ public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER"; /** * The number of tracks in the media's original source. */ public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS"; /** * The disc number for the media's original source. */ public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER"; /** * The artist for the album of the media's original source. */ public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST"; /** * The artwork for the media as a {@link Bitmap}. *
* The artwork should be relatively small and may be scaled down by the * system if it is too large. For higher resolution artwork * {@link #METADATA_KEY_ART_URI} should be used instead. */ public static final String METADATA_KEY_ART = "android.media.metadata.ART"; /** * The artwork for the media as a Uri formatted String. The artwork can be * loaded using a combination of {@link ContentResolver#openInputStream} and * {@link BitmapFactory#decodeStream}. *
* For the best results, Uris should use the content:// style and support * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. */ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI"; /** * The artwork for the album of the media's original source as a * {@link Bitmap}. *
* The artwork should be relatively small and may be scaled down by the * system if it is too large. For higher resolution artwork * {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead. */ public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART"; /** * The artwork for the album of the media's original source as a Uri * formatted String. The artwork can be loaded using a combination of * {@link ContentResolver#openInputStream} and * {@link BitmapFactory#decodeStream}. *
* For the best results, Uris should use the content:// style and support * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. */ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI"; /** * The user's rating for the media. * * @see Rating */ public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING"; /** * The overall rating for the media. * * @see Rating */ public static final String METADATA_KEY_RATING = "android.media.metadata.RATING"; /** * A title that is suitable for display to the user. This will generally be * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats. * When displaying media described by this metadata this should be preferred * if present. */ public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE"; /** * A subtitle that is suitable for display to the user. When displaying a * second line for media described by this metadata this should be preferred * to other fields if present. */ public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE"; /** * A description that is suitable for display to the user. When displaying * more information for media described by this metadata this should be * preferred to other fields if present. */ public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION"; /** * An icon or thumbnail that is suitable for display to the user. When * displaying an icon for media described by this metadata this should be * preferred to other fields if present. This must be a {@link Bitmap}. *
* The icon should be relatively small and may be scaled down by the system * if it is too large. For higher resolution artwork * {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead. */ public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON"; /** * A Uri formatted String for an icon or thumbnail that is suitable for * display to the user. When displaying more information for media described * by this metadata the display description should be preferred to other * fields when present. The icon can be loaded using a combination of * {@link ContentResolver#openInputStream} and * {@link BitmapFactory#decodeStream}. *
* For the best results, Uris should use the content:// style and support * {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}. */ public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI"; /** * A String key for identifying the content. This value is specific to the * service providing the content. If used, this should be a persistent * unique key for the underlying content. It may be used with * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} * to initiate playback when provided by a {@link MediaBrowser} connected to * the same app. */ public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"; /** * A Uri formatted String representing the content. This value is specific to the * service providing the content. It may be used with * {@link MediaController.TransportControls#playFromUri(Uri, Bundle)} * to initiate playback when provided by a {@link MediaBrowser} connected to * the same app. */ public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI"; /** * The bluetooth folder type of the media specified in the section 6.10.2.2 of the Bluetooth * AVRCP 1.5. It should be one of the following: *
* If it returns {@link Integer#MAX_VALUE}, then no scaling down was applied to the bitmaps * when this metadata was created. *
* If it returns another positive value, then all the bitmaps in this metadata has width/height * not greater than this limit. Bitmaps may have been scaled down according to the limit. *
*
* @see Builder#setBitmapDimensionLimit(int)
*/
public @IntRange(from = 1) int getBitmapDimensionLimit() {
return mBitmapDimensionLimit;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBundle(mBundle);
dest.writeInt(mBitmapDimensionLimit);
}
/**
* Returns the number of fields in this metadata.
*
* @return The number of fields in the metadata.
*/
public int size() {
return mBundle.size();
}
/**
* Returns a Set containing the Strings used as keys in this metadata.
*
* @return a Set of String keys
*/
public Set
* Uris for artwork should use the content:// style and support
* {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork
* through {@link ContentResolver#openTypedAssetFileDescriptor(Uri,
* String, Bundle)}.
*
* @param key The key for referencing this value
* @param value The String value to store
* @return The Builder to allow chaining
*/
public Builder putString(@TextKey String key, String value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a String");
}
}
mBundle.putCharSequence(key, value);
return this;
}
/**
* Put a long value into the metadata. Custom keys may be used, but if
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
*
* Large bitmaps may be scaled down by the system with
* {@link Builder#setBitmapDimensionLimit(int)} when {@link MediaSession#setMetadata}
* is called. To pass full resolution images {@link Uri Uris} should be used with
* {@link #putString}.
*
* @param key The key for referencing this value
* @param value The Bitmap to store
* @return The Builder to allow chaining
*/
public Builder putBitmap(@BitmapKey String key, Bitmap value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a Bitmap");
}
}
mBundle.putParcelable(key, value);
return this;
}
/**
* Sets the maximum width/height (in pixels) for the bitmaps in the metadata.
* Bitmaps will be replaced with scaled down copies if their width (or height) is
* larger than {@code bitmapDimensionLimit}.
*
* In order to unset the limit, pass {@link Integer#MAX_VALUE} as
* {@code bitmapDimensionLimit}.
*
* @param bitmapDimensionLimit The maximum width/height (in pixels) for bitmaps
* contained in the metadata. Non-positive values are ignored.
* Pass {@link Integer#MAX_VALUE} to unset the limit.
*/
@NonNull
public Builder setBitmapDimensionLimit(@IntRange(from = 1) int bitmapDimensionLimit) {
if (bitmapDimensionLimit > 0) {
mBitmapDimensionLimit = bitmapDimensionLimit;
} else {
Log.w(TAG, "setBitmapDimensionLimit(): Ignoring non-positive bitmapDimensionLimit: "
+ bitmapDimensionLimit);
}
return this;
}
/**
* Creates a {@link MediaMetadata} instance with the specified fields.
*
* @return The new MediaMetadata instance
*/
public MediaMetadata build() {
if (mBitmapDimensionLimit != Integer.MAX_VALUE) {
for (String key : mBundle.keySet()) {
Object value = mBundle.get(key);
if (value instanceof Bitmap) {
Bitmap bmp = (Bitmap) value;
if (bmp.getHeight() > mBitmapDimensionLimit
|| bmp.getWidth() > mBitmapDimensionLimit) {
putBitmap(key, scaleBitmap(bmp, mBitmapDimensionLimit));
}
}
}
}
return new MediaMetadata(mBundle, mBitmapDimensionLimit);
}
private Bitmap scaleBitmap(Bitmap bmp, int maxDimension) {
float maxDimensionF = maxDimension;
float widthScale = maxDimensionF / bmp.getWidth();
float heightScale = maxDimensionF / bmp.getHeight();
float scale = Math.min(widthScale, heightScale);
int height = (int) (bmp.getHeight() * scale);
int width = (int) (bmp.getWidth() * scale);
return Bitmap.createScaledBitmap(bmp, width, height, true);
}
}
}
*
*
* @param key The key for referencing this value
* @param value The CharSequence value to store
* @return The Builder to allow chaining
*/
public Builder putText(@TextKey String key, CharSequence value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a CharSequence");
}
}
mBundle.putCharSequence(key, value);
return this;
}
/**
* Put a String value into the metadata. Custom keys may be used, but if
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
*
*
*
*
*
* @param key The key for referencing this value
* @param value The long value to store
* @return The Builder to allow chaining
*/
public Builder putLong(@LongKey String key, long value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a long");
}
}
mBundle.putLong(key, value);
return this;
}
/**
* Put a {@link Rating} into the metadata. Custom keys may be used, but
* if the METADATA_KEYs defined in this class are used they may only be
* one of the following:
*
*
*
* @param key The key for referencing this value
* @param value The Rating value to store
* @return The Builder to allow chaining
*/
public Builder putRating(@RatingKey String key, Rating value) {
if (METADATA_KEYS_TYPE.containsKey(key)) {
if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
throw new IllegalArgumentException("The " + key
+ " key cannot be used to put a Rating");
}
}
mBundle.putParcelable(key, value);
return this;
}
/**
* Put a {@link Bitmap} into the metadata. Custom keys may be used, but
* if the METADATA_KEYs defined in this class are used they may only be
* one of the following:
*
*
*