/* * Copyright 2019 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 androidx.media2.common; import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.annotation.StringDef; import androidx.collection.ArrayMap; import androidx.versionedparcelable.CustomVersionedParcelable; import androidx.versionedparcelable.NonParcelField; import androidx.versionedparcelable.ParcelField; import androidx.versionedparcelable.ParcelImpl; import androidx.versionedparcelable.ParcelUtils; import androidx.versionedparcelable.VersionedParcelable; import androidx.versionedparcelable.VersionedParcelize; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Contains metadata about an item, such as the title, artist, etc. This is optional, but you'd * better to provide this as much as possible when you're using media widget and/or session APIs. *

* The media widget components build its UI based on the metadata here. For an example, * {@link androidx.media2.widget.MediaControlView} will show title from the metadata. *

* The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require * some metadata values when it provides * {@link MediaItem}s to {@link androidx.media2.session.MediaBrowser}. *

* Topics covered here: *

    *
  1. Media ID *
  2. Browsable type *
  3. Playable *
  4. Duration *
  5. User rating *
*

{@link MediaMetadata#METADATA_KEY_MEDIA_ID Media ID}

*

* If set, the media ID must be the persistent key for the underlying media contents, so * {@link androidx.media2.session.MediaController} and {@link androidx.media2.session.MediaBrowser} * can store the information and reuse it later. Some APIs requires a media ID (e.g. * {@link androidx.media2.session.MediaController#setRating}, so you'd better specify * one. *

* Typical example of using media ID is the URI of the contents, but use it with the caution because * the metadata is shared across the process in plain text. *

* The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require it * for the library root, so {@link androidx.media2.session.MediaBrowser} can call subsequent * {@link androidx.media2.session.MediaBrowser#getChildren} with the ID. *

{@link MediaMetadata#METADATA_KEY_BROWSABLE Browsable type}

*

* Browsable defines whether the media item has children and type of children if any. With this, * {@link androidx.media2.session.MediaBrowser} can know whether the subsequent * {@link androidx.media2.session.MediaBrowser#getChildren} would successfully run. *

* The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require * the explicit browsable type for the media items returned by the * {@link * androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback}. *

{@link MediaMetadata#METADATA_KEY_PLAYABLE Playable type}

*

* Playable defines whether the media item can be played or not. It may be possible for a playlist * to contain a media item which isn't playable in order to show a disabled media item. *

* The {@link androidx.media2.session.MediaLibraryService.MediaLibrarySession} would require * the explicit playable value for the media items returned by the * {@link * androidx.media2.session.MediaLibraryService.MediaLibrarySession.MediaLibrarySessionCallback}. *

{@link MediaMetadata#METADATA_KEY_DURATION Duration}

* The duration is the length of the contents. The {@link androidx.media2.session.MediaController} * can only get the duration through the metadata. This tells when would the playback ends, and also * tells about the allowed range of {@link androidx.media2.session.MediaController#seekTo(long)}. *

* If it's not set by developer, {@link androidx.media2.session.MediaSession} would update * the duration in the metadata with the {@link SessionPlayer#getDuration()}. *

{@link MediaMetadata#METADATA_KEY_USER_RATING User rating}

*

* Prefer to have unrated user rating instead of {@code null}, so * {@link androidx.media2.session.MediaController} can know the possible user rating type * for calling {@link androidx.media2.session.MediaController#setRating(String, Rating)}. */ // New version of MediaMetadata with following changes // - Don't implement Parcelable for updatable support. // - Also support MediaDescription features. MediaDescription is deprecated instead because // it was insufficient for controller to display media contents. (e.g. duration is missing) @VersionedParcelize(isCustom = true) public final class MediaMetadata extends CustomVersionedParcelable { private static final String TAG = "MediaMetadata"; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the title of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_TITLE = android.media.MediaMetadata.METADATA_KEY_TITLE; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the artist of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_ARTIST = android.media.MediaMetadata.METADATA_KEY_ARTIST; /** * The metadata key for a {@link Long} typed value to retrieve the information about the * duration of the media in ms. A negative duration indicates that the duration is unknown * (or infinite). * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_DURATION = android.media.MediaMetadata.METADATA_KEY_DURATION; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the album title for the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_ALBUM = android.media.MediaMetadata.METADATA_KEY_ALBUM; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the author of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_AUTHOR = android.media.MediaMetadata.METADATA_KEY_AUTHOR; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the writer of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_WRITER = android.media.MediaMetadata.METADATA_KEY_WRITER; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the composer of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_COMPOSER = android.media.MediaMetadata.METADATA_KEY_COMPOSER; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the compilation status of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_COMPILATION = android.media.MediaMetadata.METADATA_KEY_COMPILATION; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the date the media was created or published. * The format is unspecified but RFC 3339 is recommended. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_DATE = android.media.MediaMetadata.METADATA_KEY_DATE; /** * The metadata key for a {@link Long} typed value to retrieve the information about the year * the media was created or published. * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_YEAR = android.media.MediaMetadata.METADATA_KEY_YEAR; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the genre of the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_GENRE = android.media.MediaMetadata.METADATA_KEY_GENRE; /** * The metadata key for a {@link Long} typed value to retrieve the information about the * track number for the media. * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_TRACK_NUMBER = android.media.MediaMetadata.METADATA_KEY_TRACK_NUMBER; /** * The metadata key for a {@link Long} typed value to retrieve the information about the * number of tracks in the media's original source. * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_NUM_TRACKS = android.media.MediaMetadata.METADATA_KEY_NUM_TRACKS; /** * The metadata key for a {@link Long} typed value to retrieve the information about the * disc number for the media's original source. * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_DISC_NUMBER = android.media.MediaMetadata.METADATA_KEY_DISC_NUMBER; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the artist for the album of the media's original source. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_ALBUM_ARTIST = android.media.MediaMetadata.METADATA_KEY_ALBUM_ARTIST; /** * The metadata key for a {@link Bitmap} typed value to retrieve the information about the * artwork for the media. * The artwork should be relatively small and may be scaled down if it is too large. * For higher resolution artwork, {@link #METADATA_KEY_ART_URI} should be used instead. * * @see Builder#putBitmap(String, Bitmap) * @see #getBitmap(String) */ public static final String METADATA_KEY_ART = android.media.MediaMetadata.METADATA_KEY_ART; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about Uri of the artwork for the media. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_ART_URI = android.media.MediaMetadata.METADATA_KEY_ART_URI; /** * The metadata key for a {@link Bitmap} typed value to retrieve the information about the * artwork for the album of the media's original source. * The artwork should be relatively small and may be scaled down if it is too large. * For higher resolution artwork, {@link #METADATA_KEY_ALBUM_ART_URI} should be used instead. * * @see Builder#putBitmap(String, Bitmap) * @see #getBitmap(String) */ public static final String METADATA_KEY_ALBUM_ART = android.media.MediaMetadata.METADATA_KEY_ALBUM_ART; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the Uri of the artwork for the album of the media's original source. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_ALBUM_ART_URI = android.media.MediaMetadata.METADATA_KEY_ALBUM_ART_URI; /** * The metadata key for a {@link Rating} typed value to retrieve the information about the * user's rating for the media. Prefer to have unrated user rating instead of {@code null}, so * {@link androidx.media2.session.MediaController} can know the possible user rating type. * * @see Builder#putRating(String, Rating) * @see #getRating(String) * @see User rating */ public static final String METADATA_KEY_USER_RATING = android.media.MediaMetadata.METADATA_KEY_USER_RATING; /** * The metadata key for a {@link Rating} typed value to retrieve the information about the * overall rating for the media. * * @see Builder#putRating(String, Rating) * @see #getRating(String) */ public static final String METADATA_KEY_RATING = android.media.MediaMetadata.METADATA_KEY_RATING; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the title that is suitable for display to the user. * It 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. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_DISPLAY_TITLE = android.media.MediaMetadata.METADATA_KEY_DISPLAY_TITLE; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the 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. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_DISPLAY_SUBTITLE = android.media.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the 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. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_DISPLAY_DESCRIPTION = android.media.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION; /** * The metadata key for a {@link Bitmap} typed value to retrieve the information about the 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. *

* The icon should be relatively small and may be scaled down if it is too large. * For higher resolution artwork, {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead. * * @see Builder#putBitmap(String, Bitmap) * @see #getBitmap(String) */ public static final String METADATA_KEY_DISPLAY_ICON = android.media.MediaMetadata.METADATA_KEY_DISPLAY_ICON; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the Uri of 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. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_DISPLAY_ICON_URI = android.media.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the media ID of the content. This value is specific to the * service providing the content. If used, this should be a persistent key for the underlying * content. This ID is used by {@link androidx.media2.session.MediaController} and * {@link androidx.media2.session.MediaBrowser}. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) * @see Media ID */ public static final String METADATA_KEY_MEDIA_ID = android.media.MediaMetadata.METADATA_KEY_MEDIA_ID; /** * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the Uri of the content. This value is specific to the service providing the * content. * * @see Builder#putText(String, CharSequence) * @see Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ public static final String METADATA_KEY_MEDIA_URI = android.media.MediaMetadata.METADATA_KEY_MEDIA_URI; /** * @hide * The metadata key for a {@link Float} typed value to retrieve the information about the * radio frequency if this metadata represents radio content. * * @see Builder#putFloat(String, float) * @see #getFloat(String) */ @RestrictTo(LIBRARY) public static final String METADATA_KEY_RADIO_FREQUENCY = "androidx.media2.metadata.RADIO_FREQUENCY"; /** * @hide * The metadata key for a {@link CharSequence} or {@link String} typed value to retrieve the * information about the radio program name if this metadata represents radio content. * * @see MediaMetadata.Builder#putText(String, CharSequence) * @see MediaMetadata.Builder#putString(String, String) * @see #getText(String) * @see #getString(String) */ @RestrictTo(LIBRARY) public static final String METADATA_KEY_RADIO_PROGRAM_NAME = "androidx.media2.metadata.RADIO_PROGRAM_NAME"; /** * The metadata key for a {@link Long} typed value to retrieve the information about the type * of browsable. It should be one of the following: *

*

* The values other than {@link #BROWSABLE_TYPE_NONE} mean that the media item has children.[ *

* This matches with the bluetooth folder type of the media specified in the section 6.10.2.2 of * the Bluetooth AVRCP 1.5. * * @see Builder#putLong(String, long) * @see #getLong(String) * @see Browsable */ public static final String METADATA_KEY_BROWSABLE = "androidx.media2.metadata.BROWSABLE"; /** * The type of browsable for non-browsable media item. */ public static final long BROWSABLE_TYPE_NONE = -1; /** * The type of browsable that is unknown or contains media items of mixed types. *

* This value matches with the folder type 'Mixed' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_MIXED = 0; /** * The type of browsable that only contains playable media items. *

* This value matches with the folder type 'Titles' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_TITLES = 1; /** * The type of browsable that contains browsable items categorized by album. *

* This value matches with the folder type 'Albums' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_ALBUMS = 2; /** * The type of browsable that contains browsable items categorized by artist. *

* This value matches with the folder type 'Artists' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_ARTISTS = 3; /** * The type of browsable that contains browsable items categorized by genre. *

* This value matches with the folder type 'Genres' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_GENRES = 4; /** * The type of browsable that contains browsable items categorized by playlist. *

* This value matches with the folder type 'Playlists' as specified in the section 6.10.2.2 of * the Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_PLAYLISTS = 5; /** * The type of browsable that contains browsable items categorized by year. *

* This value matches with the folder type 'Years' as specified in the section 6.10.2.2 of the * Bluetooth AVRCP 1.5. */ public static final long BROWSABLE_TYPE_YEARS = 6; /** * The metadata key for a {@link Long} typed value to retrieve the information about whether * the media is playable. A value of 0 indicates it is not a playable item. * A value of 1 or non-zero indicates it is playable. * * @see Builder#putLong(String, long) * @see #getLong(String) * @see Playable */ public static final String METADATA_KEY_PLAYABLE = "androidx.media2.metadata.PLAYABLE"; /** * The metadata key for a {@link Long} typed value to retrieve the information about whether * the media is an advertisement. A value of 0 indicates it is not an advertisement. * A value of 1 or non-zero indicates it is an advertisement. * If not specified, this value is set to 0 by default. * * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_ADVERTISEMENT = "androidx.media2.metadata.ADVERTISEMENT"; /** * The metadata key for a {@link Long} typed value to retrieve the information about the * download status of the media which will be used for later offline playback. It should be * one of the following: * *

* * @see Builder#putLong(String, long) * @see #getLong(String) */ public static final String METADATA_KEY_DOWNLOAD_STATUS = "androidx.media2.metadata.DOWNLOAD_STATUS"; /** * The status value to indicate the media item is not downloaded. * * @see #METADATA_KEY_DOWNLOAD_STATUS */ public static final long STATUS_NOT_DOWNLOADED = 0; /** * The status value to indicate the media item is being downloaded. * * @see #METADATA_KEY_DOWNLOAD_STATUS */ public static final long STATUS_DOWNLOADING = 1; /** * The status value to indicate the media item is downloaded for later offline playback. * * @see #METADATA_KEY_DOWNLOAD_STATUS */ public static final long STATUS_DOWNLOADED = 2; /** * A {@link Bundle} extra. */ public static final String METADATA_KEY_EXTRAS = "androidx.media2.metadata.EXTRAS"; /** * @hide */ @RestrictTo(LIBRARY) @StringDef({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, METADATA_KEY_RADIO_PROGRAM_NAME}) @Retention(RetentionPolicy.SOURCE) public @interface TextKey {} /** * @hide */ @RestrictTo(LIBRARY) @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER, METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER, METADATA_KEY_BROWSABLE, METADATA_KEY_PLAYABLE, METADATA_KEY_ADVERTISEMENT, METADATA_KEY_DOWNLOAD_STATUS}) @Retention(RetentionPolicy.SOURCE) public @interface LongKey {} /** * @hide */ @RestrictTo(LIBRARY) @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON}) @Retention(RetentionPolicy.SOURCE) public @interface BitmapKey {} /** * @hide */ @RestrictTo(LIBRARY) @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING}) @Retention(RetentionPolicy.SOURCE) public @interface RatingKey {} /** * @hide */ @RestrictTo(LIBRARY) @StringDef({METADATA_KEY_RADIO_FREQUENCY}) @Retention(RetentionPolicy.SOURCE) public @interface FloatKey {} /** * @hide */ @RestrictTo(LIBRARY) @StringDef({METADATA_KEY_EXTRAS}) @Retention(RetentionPolicy.SOURCE) public @interface BundleKey {} static final int METADATA_TYPE_LONG = 0; static final int METADATA_TYPE_TEXT = 1; static final int METADATA_TYPE_BITMAP = 2; static final int METADATA_TYPE_RATING = 3; static final int METADATA_TYPE_FLOAT = 4; static final int METADATA_TYPE_BUNDLE = 5; static final ArrayMap METADATA_KEYS_TYPE; static { METADATA_KEYS_TYPE = new ArrayMap<>(); 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_MEDIA_ID, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_FREQUENCY, METADATA_TYPE_FLOAT); METADATA_KEYS_TYPE.put(METADATA_KEY_RADIO_PROGRAM_NAME, METADATA_TYPE_TEXT); METADATA_KEYS_TYPE.put(METADATA_KEY_BROWSABLE, METADATA_TYPE_LONG); METADATA_KEYS_TYPE.put(METADATA_KEY_PLAYABLE, METADATA_TYPE_LONG); METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG); METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG); METADATA_KEYS_TYPE.put(METADATA_KEY_EXTRAS, METADATA_TYPE_BUNDLE); } // Parceled via mParcelableNoBitmapBundle for non-Bitmap values, and mBitmapListSlice for Bitmap // values. @NonParcelField Bundle mBundle; // For parceling mBundle's non-Bitmap values. Should be only used by onPreParceling() and // onPostParceling(). @ParcelField(1) Bundle mParcelableWithoutBitmapBundle; // For parceling mBundle's Bitmap values. Should be only used by onPreParceling() and // onPostParceling(). @ParcelField(2) ParcelImplListSlice mBitmapListSlice; // WARNING: Adding a new ParcelField may break old library users (b/152830728) /** * Used for VersionedParcelable */ MediaMetadata() { } MediaMetadata(Bundle bundle) { mBundle = new Bundle(bundle); mBundle.setClassLoader(MediaMetadata.class.getClassLoader()); } /** * 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(@NonNull String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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 @Nullable CharSequence getText(@NonNull @TextKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } return mBundle.getCharSequence(key); } /** * Returns the media id, or {@code null} if the id doesn't exist. *

* This is equivalent to the {@link #getString(String)} with the {@link #METADATA_KEY_MEDIA_ID}. * * @return media id. Can be {@code null} * @see #METADATA_KEY_MEDIA_ID */ // TODO(jaewan): Hide -- no setMediaId() public @Nullable String getMediaId() { return getString(METADATA_KEY_MEDIA_ID); } /** * 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 String value, or null */ public @Nullable String getString(@NonNull @TextKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } CharSequence text = mBundle.getCharSequence(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(@NonNull @LongKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } return mBundle.getLong(key, 0); } /** * Return a {@link Rating} for the given key or null if no rating exists for * the given key. *

* For the {@link #METADATA_KEY_USER_RATING}, A {@code null} return value means that user rating * cannot be set by {@link androidx.media2.session.MediaController}. * * @param key The key the value is stored under * @return A {@link Rating} or {@code null} */ public @Nullable Rating getRating(@NonNull @RatingKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } Rating rating = null; try { rating = ParcelUtils.getVersionedParcelable(mBundle, key); } catch (Exception e) { // ignore, value was not a rating Log.w(TAG, "Failed to retrieve a key as Rating.", e); } return rating; } /** * Return the value associated with the given key, or 0.0f if no long exists * for the given key. * * @param key The key the value is stored under * @return a float value */ public float getFloat(@NonNull @FloatKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } return mBundle.getFloat(key); } /** * Return 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 @Nullable Bitmap getBitmap(@NonNull @BitmapKey String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } Bitmap bmp = null; try { bmp = mBundle.getParcelable(key); } catch (Exception e) { // ignore, value was not a bitmap Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); } return bmp; } /** * Get the extra {@link Bundle} from the metadata object. * * @return A {@link Bundle} or {@code null} */ public @Nullable Bundle getExtras() { try { return mBundle.getBundle(METADATA_KEY_EXTRAS); } catch (Exception e) { // ignore, value was not an bundle Log.w(TAG, "Failed to retrieve an extra"); } return null; } /** * Get 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 @NonNull Set keySet() { return mBundle.keySet(); } @Override public String toString() { return mBundle.toString(); } /** * Gets the object which matches the given key in the backing bundle. * * @hide */ @RestrictTo(LIBRARY_GROUP) public @Nullable Object getObject(@NonNull String key) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } return mBundle.get(key); } /** * @hide */ @Override @RestrictTo(LIBRARY) @SuppressWarnings("SynchronizeOnNonFinalField") // mBundle is effectively final. public void onPreParceling(boolean isStream) { synchronized (mBundle) { if (mParcelableWithoutBitmapBundle == null) { mParcelableWithoutBitmapBundle = new Bundle(mBundle); List parcelImplList = new ArrayList<>(); for (String key : mBundle.keySet()) { Object value = mBundle.get(key); if (!(value instanceof Bitmap)) { // Note: Null bitmap is sent through mParcelableNoBitmapBundle. continue; } Bitmap bitmap = (Bitmap) value; parcelImplList.add(MediaParcelUtils.toParcelable(new BitmapEntry(key, bitmap))); mParcelableWithoutBitmapBundle.remove(key); } mBitmapListSlice = new ParcelImplListSlice(parcelImplList); } } } /** * @hide */ @Override @RestrictTo(LIBRARY) public void onPostParceling() { mBundle = (mParcelableWithoutBitmapBundle != null) ? mParcelableWithoutBitmapBundle : new Bundle(); if (mBitmapListSlice != null) { List parcelImplList = mBitmapListSlice.getList(); for (ParcelImpl parcelImpl : parcelImplList) { BitmapEntry entry = MediaParcelUtils.fromParcelable(parcelImpl); mBundle.putParcelable(entry.getKey(), entry.getBitmap()); } } } /** * Use to build MediaMetadatax objects. The system defined metadata keys must * use the appropriate data type. */ public static final class Builder { final Bundle mBundle; /** * 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(@NonNull MediaMetadata source) { mBundle = new Bundle(source.mBundle); } /** * Only for the backward compatibility. * * @param bundle */ Builder(Bundle bundle) { mBundle = new Bundle(bundle); } /** * 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: *

* * @param key The key for referencing this value * @param value The CharSequence value to store * @return The Builder to allow chaining */ public @NonNull Builder putText(@NonNull @TextKey String key, @Nullable CharSequence value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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 String value to store * @return The Builder to allow chaining */ public @NonNull Builder putString(@NonNull @TextKey String key, @Nullable String value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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: * * * @param key The key for referencing this value * @param value The String value to store * @return The Builder to allow chaining */ public @NonNull Builder putLong(@NonNull @LongKey String key, long value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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 String value to store * @return The Builder to allow chaining */ public @NonNull Builder putRating(@NonNull @RatingKey String key, @Nullable Rating value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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"); } } ParcelUtils.putVersionedParcelable(mBundle, 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: * * Large bitmaps may be scaled down when it is passed to the other process. * 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 @NonNull Builder putBitmap(@NonNull @BitmapKey String key, @Nullable Bitmap value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } 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; } /** * Put a float value into the metadata. Custom keys may be used. * * @param key The key for referencing this value * @param value The float value to store * @return The Builder to allow chaining */ public @NonNull Builder putFloat(@NonNull @LongKey String key, float value) { if (key == null) { throw new NullPointerException("key shouldn't be null"); } if (METADATA_KEYS_TYPE.containsKey(key)) { if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_FLOAT) { throw new IllegalArgumentException("The " + key + " key cannot be used to put a float"); } } mBundle.putFloat(key, value); return this; } /** * Set a bundle of extras. * * @param extras The extras to include with this description or null. * @return The Builder to allow chaining */ public @NonNull Builder setExtras(@Nullable Bundle extras) { mBundle.putBundle(METADATA_KEY_EXTRAS, extras); return this; } /** * Creates a {@link MediaMetadata} instance with the specified fields. * * @return The new MediaMetadatax instance */ public @NonNull MediaMetadata build() { return new MediaMetadata(mBundle); } } /** * Stores a bitmap and the matching key. Used for sending bitmaps to other process one-by-one. */ @VersionedParcelize static final class BitmapEntry implements VersionedParcelable { static final int BITMAP_SIZE_LIMIT_IN_BYTES = 256 * 1024; // 256 KB @ParcelField(1) String mKey; @ParcelField(2) Bitmap mBitmap; // WARNING: Adding a new ParcelField may break old library users (b/152830728) /** * Used for VersionedParcelable */ BitmapEntry() { } BitmapEntry(@NonNull String key, @NonNull Bitmap bitmap) { mKey = key; mBitmap = bitmap; // Scale bitmap if it is large. final int sizeInBytes = getBitmapSizeInBytes(mBitmap); if (sizeInBytes > BITMAP_SIZE_LIMIT_IN_BYTES) { int oldWidth = bitmap.getWidth(); int oldHeight = bitmap.getHeight(); double scaleFactor = Math.sqrt(BITMAP_SIZE_LIMIT_IN_BYTES / (double) sizeInBytes); int newWidth = (int) (oldWidth * scaleFactor); int newHeight = (int) (oldHeight * scaleFactor); Log.i(TAG, "Scaling large bitmap of " + oldWidth + "x" + oldHeight + " into " + newWidth + "x" + newHeight); mBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); } } String getKey() { return mKey; } Bitmap getBitmap() { return mBitmap; } private int getBitmapSizeInBytes(Bitmap bitmap) { if (Build.VERSION.SDK_INT >= 19) { return bitmap.getAllocationByteCount(); } else { return bitmap.getByteCount(); } } } }