989 lines
38 KiB
Java
989 lines
38 KiB
Java
/*
|
|
* 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}.
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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:
|
|
* <ul>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_MIXED}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_TITLES}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_ALBUMS}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_ARTISTS}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_GENRES}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_PLAYLISTS}</li>
|
|
* <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
|
|
* </ul>
|
|
*/
|
|
public static final String METADATA_KEY_BT_FOLDER_TYPE =
|
|
"android.media.metadata.BT_FOLDER_TYPE";
|
|
|
|
private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
|
|
METADATA_KEY_TITLE,
|
|
METADATA_KEY_ARTIST,
|
|
METADATA_KEY_ALBUM,
|
|
METADATA_KEY_ALBUM_ARTIST,
|
|
METADATA_KEY_WRITER,
|
|
METADATA_KEY_AUTHOR,
|
|
METADATA_KEY_COMPOSER
|
|
};
|
|
|
|
private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
|
|
METADATA_KEY_DISPLAY_ICON,
|
|
METADATA_KEY_ART,
|
|
METADATA_KEY_ALBUM_ART
|
|
};
|
|
|
|
private static final @TextKey String[] PREFERRED_URI_ORDER = {
|
|
METADATA_KEY_DISPLAY_ICON_URI,
|
|
METADATA_KEY_ART_URI,
|
|
METADATA_KEY_ALBUM_ART_URI
|
|
};
|
|
|
|
private static final int METADATA_TYPE_INVALID = -1;
|
|
private static final int METADATA_TYPE_LONG = 0;
|
|
private static final int METADATA_TYPE_TEXT = 1;
|
|
private static final int METADATA_TYPE_BITMAP = 2;
|
|
private static final int METADATA_TYPE_RATING = 3;
|
|
private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
|
|
|
|
static {
|
|
METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
|
|
METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT);
|
|
}
|
|
|
|
private static final SparseArray<String> EDITOR_KEY_MAPPING;
|
|
|
|
static {
|
|
EDITOR_KEY_MAPPING = new SparseArray<String>();
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.BITMAP_KEY_ARTWORK, METADATA_KEY_ART);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_OTHERS, METADATA_KEY_RATING);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataEditor.RATING_KEY_BY_USER, METADATA_KEY_USER_RATING);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUM, METADATA_KEY_ALBUM);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
|
|
METADATA_KEY_ALBUM_ARTIST);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_ARTIST, METADATA_KEY_ARTIST);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_AUTHOR, METADATA_KEY_AUTHOR);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
|
|
METADATA_KEY_TRACK_NUMBER);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPOSER, METADATA_KEY_COMPOSER);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
|
|
METADATA_KEY_COMPILATION);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DATE, METADATA_KEY_DATE);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
|
|
METADATA_KEY_DISC_NUMBER);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_DURATION, METADATA_KEY_DURATION);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_GENRE, METADATA_KEY_GENRE);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
|
|
METADATA_KEY_NUM_TRACKS);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_TITLE, METADATA_KEY_TITLE);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_WRITER, METADATA_KEY_WRITER);
|
|
EDITOR_KEY_MAPPING.put(MediaMetadataRetriever.METADATA_KEY_YEAR, METADATA_KEY_YEAR);
|
|
}
|
|
|
|
private final Bundle mBundle;
|
|
private final int mBitmapDimensionLimit;
|
|
private MediaDescription mDescription;
|
|
|
|
private MediaMetadata(Bundle bundle, int bitmapDimensionLimit) {
|
|
mBundle = new Bundle(bundle);
|
|
mBitmapDimensionLimit = bitmapDimensionLimit;
|
|
}
|
|
|
|
private MediaMetadata(Parcel in) {
|
|
mBundle = in.readBundle();
|
|
mBitmapDimensionLimit = Math.max(in.readInt(), 1);
|
|
|
|
// Proactively read bitmaps from known bitmap keys, to ensure that they're unparceled and
|
|
// added to mBundle's internal map. This ensures that the GC accounts for the underlying
|
|
// allocations, which it does not do if the bitmaps remain parceled (see b/215820910).
|
|
// TODO(b/223225532): Remove this workaround once the underlying allocations are properly
|
|
// tracked in NativeAllocationsRegistry.
|
|
getBitmap(METADATA_KEY_ART);
|
|
getBitmap(METADATA_KEY_ALBUM_ART);
|
|
getBitmap(METADATA_KEY_DISPLAY_ICON);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given key is contained in the metadata
|
|
*
|
|
* @param key a String key
|
|
* @return true if the key exists in this metadata, false otherwise
|
|
*/
|
|
public boolean containsKey(String key) {
|
|
return mBundle.containsKey(key);
|
|
}
|
|
|
|
/**
|
|
* Returns the value associated with the given key, or null if no mapping of
|
|
* the desired type exists for the given key or a null value is explicitly
|
|
* associated with the key.
|
|
*
|
|
* @param key The key the value is stored under
|
|
* @return a CharSequence value, or null
|
|
*/
|
|
public CharSequence getText(@TextKey String key) {
|
|
return mBundle.getCharSequence(key);
|
|
}
|
|
|
|
/**
|
|
* Returns the text value associated with the given key as a String, or null
|
|
* if no mapping of the desired type exists for the given key or a null
|
|
* value is explicitly associated with the key. This is equivalent to
|
|
* calling {@link #getText getText().toString()} if the value is not null.
|
|
*
|
|
* @param key The key the value is stored under
|
|
* @return a String value, or null
|
|
*/
|
|
public String getString(@TextKey String key) {
|
|
CharSequence text = getText(key);
|
|
if (text != null) {
|
|
return text.toString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the value associated with the given key, or 0L if no long exists
|
|
* for the given key.
|
|
*
|
|
* @param key The key the value is stored under
|
|
* @return a long value
|
|
*/
|
|
public long getLong(@LongKey String key) {
|
|
return mBundle.getLong(key, 0);
|
|
}
|
|
|
|
/**
|
|
* Returns a {@link Rating} for the given key or null if no rating exists
|
|
* for the given key.
|
|
*
|
|
* @param key The key the value is stored under
|
|
* @return A {@link Rating} or null
|
|
*/
|
|
public Rating getRating(@RatingKey String key) {
|
|
Rating rating = null;
|
|
try {
|
|
rating = mBundle.getParcelable(key, android.media.Rating.class);
|
|
} catch (Exception e) {
|
|
// ignore, value was not a bitmap
|
|
Log.w(TAG, "Failed to retrieve a key as Rating.", e);
|
|
}
|
|
return rating;
|
|
}
|
|
|
|
/**
|
|
* Returns a {@link Bitmap} for the given key or null if no bitmap exists
|
|
* for the given key.
|
|
*
|
|
* @param key The key the value is stored under
|
|
* @return A {@link Bitmap} or null
|
|
*/
|
|
public Bitmap getBitmap(@BitmapKey String key) {
|
|
Bitmap bmp = null;
|
|
try {
|
|
bmp = mBundle.getParcelable(key, android.graphics.Bitmap.class);
|
|
} catch (Exception e) {
|
|
// ignore, value was not a bitmap
|
|
Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
|
|
}
|
|
return bmp;
|
|
}
|
|
|
|
/**
|
|
* Gets the width/height limit (in pixels) for the bitmaps when this metadata was created.
|
|
* This method always returns a positive value.
|
|
* <p>
|
|
* If it returns {@link Integer#MAX_VALUE}, then no scaling down was applied to the bitmaps
|
|
* when this metadata was created.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
*
|
|
* @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<String> keySet() {
|
|
return mBundle.keySet();
|
|
}
|
|
|
|
/**
|
|
* Returns a simple description of this metadata for display purposes.
|
|
*
|
|
* @return A simple description of this metadata.
|
|
*/
|
|
public @NonNull MediaDescription getDescription() {
|
|
if (mDescription != null) {
|
|
return mDescription;
|
|
}
|
|
|
|
String mediaId = getString(METADATA_KEY_MEDIA_ID);
|
|
|
|
CharSequence[] text = new CharSequence[3];
|
|
Bitmap icon = null;
|
|
Uri iconUri = null;
|
|
|
|
// First handle the case where display data is set already
|
|
CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
|
|
if (!TextUtils.isEmpty(displayText)) {
|
|
// If they have a display title use only display data, otherwise use
|
|
// our best bets
|
|
text[0] = displayText;
|
|
text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
|
|
text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
|
|
} else {
|
|
// Use whatever fields we can
|
|
int textIndex = 0;
|
|
int keyIndex = 0;
|
|
while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
|
|
CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
|
|
if (!TextUtils.isEmpty(next)) {
|
|
// Fill in the next empty bit of text
|
|
text[textIndex++] = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the best art bitmap we can find
|
|
for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
|
|
Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
|
|
if (next != null) {
|
|
icon = next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the best Uri we can find
|
|
for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
|
|
String next = getString(PREFERRED_URI_ORDER[i]);
|
|
if (!TextUtils.isEmpty(next)) {
|
|
iconUri = Uri.parse(next);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Uri mediaUri = null;
|
|
String mediaUriStr = getString(METADATA_KEY_MEDIA_URI);
|
|
if (!TextUtils.isEmpty(mediaUriStr)) {
|
|
mediaUri = Uri.parse(mediaUriStr);
|
|
}
|
|
|
|
MediaDescription.Builder bob = new MediaDescription.Builder();
|
|
bob.setMediaId(mediaId);
|
|
bob.setTitle(text[0]);
|
|
bob.setSubtitle(text[1]);
|
|
bob.setDescription(text[2]);
|
|
bob.setIconBitmap(icon);
|
|
bob.setIconUri(iconUri);
|
|
bob.setMediaUri(mediaUri);
|
|
if (mBundle.containsKey(METADATA_KEY_BT_FOLDER_TYPE)) {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putLong(MediaDescription.EXTRA_BT_FOLDER_TYPE,
|
|
getLong(METADATA_KEY_BT_FOLDER_TYPE));
|
|
bob.setExtras(bundle);
|
|
}
|
|
mDescription = bob.build();
|
|
|
|
return mDescription;
|
|
}
|
|
|
|
/**
|
|
* Helper for getting the String key used by {@link MediaMetadata} from the
|
|
* integer key that {@link MediaMetadataEditor} uses.
|
|
*
|
|
* @param editorKey The key used by the editor
|
|
* @return The key used by this class or null if no mapping exists
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static String getKeyFromMetadataEditorKey(int editorKey) {
|
|
return EDITOR_KEY_MAPPING.get(editorKey, null);
|
|
}
|
|
|
|
public static final @android.annotation.NonNull Parcelable.Creator<MediaMetadata> CREATOR =
|
|
new Parcelable.Creator<MediaMetadata>() {
|
|
@Override
|
|
public MediaMetadata createFromParcel(Parcel in) {
|
|
return new MediaMetadata(in);
|
|
}
|
|
|
|
@Override
|
|
public MediaMetadata[] newArray(int size) {
|
|
return new MediaMetadata[size];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Compares the contents of this object to another MediaMetadata object. It
|
|
* does not compare Bitmaps and Ratings as the media player can choose to
|
|
* forgo these fields depending on how you retrieve the MediaMetadata.
|
|
*
|
|
* @param o The Metadata object to compare this object against
|
|
* @return Whether or not the two objects have matching fields (excluding
|
|
* Bitmaps and Ratings)
|
|
*/
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (o == this) {
|
|
return true;
|
|
}
|
|
|
|
if (!(o instanceof MediaMetadata)) {
|
|
return false;
|
|
}
|
|
|
|
final MediaMetadata m = (MediaMetadata) o;
|
|
|
|
for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
|
|
String key = METADATA_KEYS_TYPE.keyAt(i);
|
|
switch (METADATA_KEYS_TYPE.valueAt(i)) {
|
|
case METADATA_TYPE_TEXT:
|
|
if (!Objects.equals(getString(key), m.getString(key))) {
|
|
return false;
|
|
}
|
|
break;
|
|
case METADATA_TYPE_LONG:
|
|
if (getLong(key) != m.getLong(key)) {
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
// Ignore ratings and bitmaps when comparing
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hashCode = 17;
|
|
|
|
for (int i = 0; i < METADATA_KEYS_TYPE.size(); i++) {
|
|
String key = METADATA_KEYS_TYPE.keyAt(i);
|
|
switch (METADATA_KEYS_TYPE.valueAt(i)) {
|
|
case METADATA_TYPE_TEXT:
|
|
hashCode = 31 * hashCode + Objects.hash(getString(key));
|
|
break;
|
|
case METADATA_TYPE_LONG:
|
|
hashCode = 31 * hashCode + Long.hashCode(getLong(key));
|
|
break;
|
|
default:
|
|
// Ignore ratings and bitmaps when comparing
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hashCode;
|
|
}
|
|
|
|
/**
|
|
* Use to build MediaMetadata objects. The system defined metadata keys must
|
|
* use the appropriate data type.
|
|
*/
|
|
public static final class Builder {
|
|
private final Bundle mBundle;
|
|
private int mBitmapDimensionLimit = Integer.MAX_VALUE;
|
|
|
|
/**
|
|
* Create an empty Builder. Any field that should be included in the
|
|
* {@link MediaMetadata} must be added.
|
|
*/
|
|
public Builder() {
|
|
mBundle = new Bundle();
|
|
}
|
|
|
|
/**
|
|
* Create a Builder using a {@link MediaMetadata} instance to set the
|
|
* initial values. All fields in the source metadata will be included in
|
|
* the new metadata. Fields can be overwritten by adding the same key.
|
|
*
|
|
* @param source
|
|
*/
|
|
public Builder(MediaMetadata source) {
|
|
mBundle = new Bundle(source.mBundle);
|
|
mBitmapDimensionLimit = source.mBitmapDimensionLimit;
|
|
}
|
|
|
|
/**
|
|
* Put a CharSequence 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:
|
|
* <ul>
|
|
* <li>{@link #METADATA_KEY_TITLE}</li>
|
|
* <li>{@link #METADATA_KEY_ARTIST}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM}</li>
|
|
* <li>{@link #METADATA_KEY_AUTHOR}</li>
|
|
* <li>{@link #METADATA_KEY_WRITER}</li>
|
|
* <li>{@link #METADATA_KEY_COMPOSER}</li>
|
|
* <li>{@link #METADATA_KEY_DATE}</li>
|
|
* <li>{@link #METADATA_KEY_GENRE}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
|
|
* <li>{@link #METADATA_KEY_ART_URI}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
|
|
* </ul>
|
|
*
|
|
* @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:
|
|
* <ul>
|
|
* <li>{@link #METADATA_KEY_TITLE}</li>
|
|
* <li>{@link #METADATA_KEY_ARTIST}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM}</li>
|
|
* <li>{@link #METADATA_KEY_AUTHOR}</li>
|
|
* <li>{@link #METADATA_KEY_WRITER}</li>
|
|
* <li>{@link #METADATA_KEY_COMPOSER}</li>
|
|
* <li>{@link #METADATA_KEY_DATE}</li>
|
|
* <li>{@link #METADATA_KEY_GENRE}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
|
|
* <li>{@link #METADATA_KEY_ART_URI}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
|
|
* </ul>
|
|
* <p>
|
|
* 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:
|
|
* <ul>
|
|
* <li>{@link #METADATA_KEY_DURATION}</li>
|
|
* <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
|
|
* <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
|
|
* <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
|
|
* <li>{@link #METADATA_KEY_YEAR}</li>
|
|
* </ul>
|
|
*
|
|
* @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:
|
|
* <ul>
|
|
* <li>{@link #METADATA_KEY_RATING}</li>
|
|
* <li>{@link #METADATA_KEY_USER_RATING}</li>
|
|
* </ul>
|
|
*
|
|
* @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:
|
|
* <ul>
|
|
* <li>{@link #METADATA_KEY_ART}</li>
|
|
* <li>{@link #METADATA_KEY_ALBUM_ART}</li>
|
|
* <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
|
|
* </ul>
|
|
* <p>
|
|
* 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}.
|
|
* <p>
|
|
* 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);
|
|
}
|
|
}
|
|
}
|