1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.support.v4.media;
17
18import android.graphics.Bitmap;
19import android.net.Uri;
20import android.os.Build;
21import android.os.Bundle;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.support.annotation.StringDef;
25import android.support.v4.util.ArrayMap;
26import android.text.TextUtils;
27import android.util.Log;
28
29import java.lang.annotation.Retention;
30import java.lang.annotation.RetentionPolicy;
31import java.util.Set;
32
33/**
34 * Contains metadata about an item, such as the title, artist, etc.
35 */
36public final class MediaMetadataCompat implements Parcelable {
37    private static final String TAG = "MediaMetadata";
38
39    /**
40     * The title of the media.
41     */
42    public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
43
44    /**
45     * The artist of the media.
46     */
47    public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
48
49    /**
50     * The duration of the media in ms. A negative duration indicates that the
51     * duration is unknown (or infinite).
52     */
53    public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
54
55    /**
56     * The album title for the media.
57     */
58    public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
59
60    /**
61     * The author of the media.
62     */
63    public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
64
65    /**
66     * The writer of the media.
67     */
68    public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
69
70    /**
71     * The composer of the media.
72     */
73    public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
74
75    /**
76     * The compilation status of the media.
77     */
78    public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
79
80    /**
81     * The date the media was created or published as TODO determine format.
82     */
83    public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
84
85    /**
86     * The year the media was created or published as a long.
87     */
88    public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
89
90    /**
91     * The genre of the media.
92     */
93    public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
94
95    /**
96     * The track number for the media.
97     */
98    public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
99
100    /**
101     * The number of tracks in the media's original source.
102     */
103    public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
104
105    /**
106     * The disc number for the media's original source.
107     */
108    public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
109
110    /**
111     * The artist for the album of the media's original source.
112     */
113    public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
114
115    /**
116     * The artwork for the media as a {@link Bitmap}.
117     */
118    public static final String METADATA_KEY_ART = "android.media.metadata.ART";
119
120    /**
121     * The artwork for the media as a Uri style String.
122     */
123    public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
124
125    /**
126     * The artwork for the album of the media's original source as a
127     * {@link Bitmap}.
128     */
129    public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
130
131    /**
132     * The artwork for the album of the media's original source as a Uri style
133     * String.
134     */
135    public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
136
137    /**
138     * The user's rating for the media.
139     *
140     * @see RatingCompat
141     */
142    public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
143
144    /**
145     * The overall rating for the media.
146     *
147     * @see RatingCompat
148     */
149    public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
150
151    /**
152     * A title that is suitable for display to the user. This will generally be
153     * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
154     * When displaying media described by this metadata this should be preferred
155     * if present.
156     */
157    public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
158
159    /**
160     * A subtitle that is suitable for display to the user. When displaying a
161     * second line for media described by this metadata this should be preferred
162     * to other fields if present.
163     */
164    public static final String METADATA_KEY_DISPLAY_SUBTITLE
165            = "android.media.metadata.DISPLAY_SUBTITLE";
166
167    /**
168     * A description that is suitable for display to the user. When displaying
169     * more information for media described by this metadata this should be
170     * preferred to other fields if present.
171     */
172    public static final String METADATA_KEY_DISPLAY_DESCRIPTION
173            = "android.media.metadata.DISPLAY_DESCRIPTION";
174
175    /**
176     * An icon or thumbnail that is suitable for display to the user. When
177     * displaying an icon for media described by this metadata this should be
178     * preferred to other fields if present. This must be a {@link Bitmap}.
179     */
180    public static final String METADATA_KEY_DISPLAY_ICON
181            = "android.media.metadata.DISPLAY_ICON";
182
183    /**
184     * An icon or thumbnail that is suitable for display to the user. When
185     * displaying more information for media described by this metadata the
186     * display description should be preferred to other fields when present.
187     * This must be a Uri style String.
188     */
189    public static final String METADATA_KEY_DISPLAY_ICON_URI
190            = "android.media.metadata.DISPLAY_ICON_URI";
191
192    /**
193     * A String key for identifying the content. This value is specific to the
194     * service providing the content. If used, this should be a persistent
195     * unique key for the underlying content.
196     */
197    public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
198
199    /**
200     * @hide
201     */
202    @StringDef({METADATA_KEY_TITLE, METADATA_KEY_ARTIST, METADATA_KEY_ALBUM, METADATA_KEY_AUTHOR,
203            METADATA_KEY_WRITER, METADATA_KEY_COMPOSER, METADATA_KEY_COMPILATION,
204            METADATA_KEY_DATE, METADATA_KEY_GENRE, METADATA_KEY_ALBUM_ARTIST, METADATA_KEY_ART_URI,
205            METADATA_KEY_ALBUM_ART_URI, METADATA_KEY_DISPLAY_TITLE, METADATA_KEY_DISPLAY_SUBTITLE,
206            METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_KEY_DISPLAY_ICON_URI,
207            METADATA_KEY_MEDIA_ID})
208    @Retention(RetentionPolicy.SOURCE)
209    public @interface TextKey {}
210
211    /**
212     * @hide
213     */
214    @StringDef({METADATA_KEY_DURATION, METADATA_KEY_YEAR, METADATA_KEY_TRACK_NUMBER,
215            METADATA_KEY_NUM_TRACKS, METADATA_KEY_DISC_NUMBER})
216    @Retention(RetentionPolicy.SOURCE)
217    public @interface LongKey {}
218
219    /**
220     * @hide
221     */
222    @StringDef({METADATA_KEY_ART, METADATA_KEY_ALBUM_ART, METADATA_KEY_DISPLAY_ICON})
223    @Retention(RetentionPolicy.SOURCE)
224    public @interface BitmapKey {}
225
226    /**
227     * @hide
228     */
229    @StringDef({METADATA_KEY_USER_RATING, METADATA_KEY_RATING})
230    @Retention(RetentionPolicy.SOURCE)
231    public @interface RatingKey {}
232
233    private static final int METADATA_TYPE_LONG = 0;
234    private static final int METADATA_TYPE_TEXT = 1;
235    private static final int METADATA_TYPE_BITMAP = 2;
236    private static final int METADATA_TYPE_RATING = 3;
237    private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
238
239    static {
240        METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
241        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
242        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
243        METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
244        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
245        METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
246        METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
247        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
248        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
249        METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
250        METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
251        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
252        METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
253        METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
254        METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
255        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
256        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
257        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
258        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
259        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
260        METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
261        METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
262        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
263        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
264        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
265        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
266        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
267        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
268    }
269
270    private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
271            METADATA_KEY_TITLE,
272            METADATA_KEY_ARTIST,
273            METADATA_KEY_ALBUM,
274            METADATA_KEY_ALBUM_ARTIST,
275            METADATA_KEY_WRITER,
276            METADATA_KEY_AUTHOR,
277            METADATA_KEY_COMPOSER
278    };
279
280    private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = {
281            METADATA_KEY_DISPLAY_ICON,
282            METADATA_KEY_ART,
283            METADATA_KEY_ALBUM_ART
284    };
285
286    private static final @TextKey String[] PREFERRED_URI_ORDER = {
287            METADATA_KEY_DISPLAY_ICON_URI,
288            METADATA_KEY_ART_URI,
289            METADATA_KEY_ALBUM_ART_URI
290    };
291
292    private final Bundle mBundle;
293    private Object mMetadataObj;
294    private MediaDescriptionCompat mDescription;
295
296    private MediaMetadataCompat(Bundle bundle) {
297        mBundle = new Bundle(bundle);
298    }
299
300    private MediaMetadataCompat(Parcel in) {
301        mBundle = in.readBundle();
302    }
303
304    /**
305     * Returns true if the given key is contained in the metadata
306     *
307     * @param key a String key
308     * @return true if the key exists in this metadata, false otherwise
309     */
310    public boolean containsKey(String key) {
311        return mBundle.containsKey(key);
312    }
313
314    /**
315     * Returns the value associated with the given key, or null if no mapping of
316     * the desired type exists for the given key or a null value is explicitly
317     * associated with the key.
318     *
319     * @param key The key the value is stored under
320     * @return a CharSequence value, or null
321     */
322    public CharSequence getText(@TextKey String key) {
323        return mBundle.getCharSequence(key);
324    }
325
326    /**
327     * Returns the value associated with the given key, or null if no mapping of
328     * the desired type exists for the given key or a null value is explicitly
329     * associated with the key.
330     *
331     * @param key The key the value is stored under
332     * @return a String value, or null
333     */
334    public String getString(@TextKey String key) {
335        CharSequence text = mBundle.getCharSequence(key);
336        if (text != null) {
337            return text.toString();
338        }
339        return null;
340    }
341
342    /**
343     * Returns the value associated with the given key, or 0L if no long exists
344     * for the given key.
345     *
346     * @param key The key the value is stored under
347     * @return a long value
348     */
349    public long getLong(@LongKey String key) {
350        return mBundle.getLong(key, 0);
351    }
352
353    /**
354     * Return a {@link RatingCompat} for the given key or null if no rating exists for
355     * the given key.
356     *
357     * @param key The key the value is stored under
358     * @return A {@link RatingCompat} or null
359     */
360    public RatingCompat getRating(@RatingKey String key) {
361        RatingCompat rating = null;
362        try {
363            rating = mBundle.getParcelable(key);
364        } catch (Exception e) {
365            // ignore, value was not a bitmap
366            Log.w(TAG, "Failed to retrieve a key as Rating.", e);
367        }
368        return rating;
369    }
370
371    /**
372     * Return a {@link Bitmap} for the given key or null if no bitmap exists for
373     * the given key.
374     *
375     * @param key The key the value is stored under
376     * @return A {@link Bitmap} or null
377     */
378    public Bitmap getBitmap(@BitmapKey String key) {
379        Bitmap bmp = null;
380        try {
381            bmp = mBundle.getParcelable(key);
382        } catch (Exception e) {
383            // ignore, value was not a bitmap
384            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
385        }
386        return bmp;
387    }
388
389    /**
390     * Returns a simple description of this metadata for display purposes.
391     *
392     * @return A simple description of this metadata.
393     */
394    public MediaDescriptionCompat getDescription() {
395        if (mDescription != null) {
396            return mDescription;
397        }
398
399        String mediaId = getString(METADATA_KEY_MEDIA_ID);
400
401        CharSequence[] text = new CharSequence[3];
402        Bitmap icon = null;
403        Uri iconUri = null;
404
405        // First handle the case where display data is set already
406        CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
407        if (!TextUtils.isEmpty(displayText)) {
408            // If they have a display title use only display data, otherwise use
409            // our best bets
410            text[0] = displayText;
411            text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
412            text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
413        } else {
414            // Use whatever fields we can
415            int textIndex = 0;
416            int keyIndex = 0;
417            while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
418                CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
419                if (!TextUtils.isEmpty(next)) {
420                    // Fill in the next empty bit of text
421                    text[textIndex++] = next;
422                }
423            }
424        }
425
426        // Get the best art bitmap we can find
427        for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
428            Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
429            if (next != null) {
430                icon = next;
431                break;
432            }
433        }
434
435        // Get the best Uri we can find
436        for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
437            String next = getString(PREFERRED_URI_ORDER[i]);
438            if (!TextUtils.isEmpty(next)) {
439                iconUri = Uri.parse(next);
440                break;
441            }
442        }
443
444        MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
445        bob.setMediaId(mediaId);
446        bob.setTitle(text[0]);
447        bob.setSubtitle(text[1]);
448        bob.setDescription(text[2]);
449        bob.setIconBitmap(icon);
450        bob.setIconUri(iconUri);
451        mDescription = bob.build();
452
453        return mDescription;
454    }
455
456    @Override
457    public int describeContents() {
458        return 0;
459    }
460
461    @Override
462    public void writeToParcel(Parcel dest, int flags) {
463        dest.writeBundle(mBundle);
464    }
465
466    /**
467     * Get the number of fields in this metadata.
468     *
469     * @return The number of fields in the metadata.
470     */
471    public int size() {
472        return mBundle.size();
473    }
474
475    /**
476     * Returns a Set containing the Strings used as keys in this metadata.
477     *
478     * @return a Set of String keys
479     */
480    public Set<String> keySet() {
481        return mBundle.keySet();
482    }
483
484    /**
485     * Gets the bundle backing the metadata object. This is available to support
486     * backwards compatibility. Apps should not modify the bundle directly.
487     *
488     * @return The Bundle backing this metadata.
489     */
490    public Bundle getBundle() {
491        return mBundle;
492    }
493
494    /**
495     * Creates an instance from a framework {@link android.media.MediaMetadata}
496     * object.
497     * <p>
498     * This method is only supported on
499     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
500     * </p>
501     *
502     * @param metadataObj A {@link android.media.MediaMetadata} object, or null
503     *            if none.
504     * @return An equivalent {@link MediaMetadataCompat} object, or null if
505     *         none.
506     */
507    public static MediaMetadataCompat fromMediaMetadata(Object metadataObj) {
508        if (metadataObj == null || Build.VERSION.SDK_INT < 21) {
509            return null;
510        }
511
512        Builder builder = new Builder();
513        for (String key : MediaMetadataCompatApi21.keySet(metadataObj)) {
514            Integer type = METADATA_KEYS_TYPE.get(key);
515            if (type != null) {
516                switch (type) {
517                    case METADATA_TYPE_BITMAP:
518                        builder.putBitmap(key,
519                                MediaMetadataCompatApi21.getBitmap(metadataObj, key));
520                        break;
521                    case METADATA_TYPE_LONG:
522                        builder.putLong(key,
523                                MediaMetadataCompatApi21.getLong(metadataObj, key));
524                        break;
525                    case METADATA_TYPE_RATING:
526                        builder.putRating(key, RatingCompat.fromRating(
527                                MediaMetadataCompatApi21.getRating(metadataObj, key)));
528                        break;
529                    case METADATA_TYPE_TEXT:
530                        builder.putText(key,
531                                MediaMetadataCompatApi21.getText(metadataObj, key));
532                        break;
533                }
534            }
535        }
536        MediaMetadataCompat metadata = builder.build();
537        metadata.mMetadataObj = metadataObj;
538        return metadata;
539    }
540
541    /**
542     * Gets the underlying framework {@link android.media.MediaMetadata} object.
543     * <p>
544     * This method is only supported on
545     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
546     * </p>
547     *
548     * @return An equivalent {@link android.media.MediaMetadata} object, or null
549     *         if none.
550     */
551    public Object getMediaMetadata() {
552        if (mMetadataObj != null || Build.VERSION.SDK_INT < 21) {
553            return mMetadataObj;
554        }
555
556        Object builderObj = MediaMetadataCompatApi21.Builder.newInstance();
557        for (String key : keySet()) {
558            Integer type = METADATA_KEYS_TYPE.get(key);
559            if (type != null) {
560                switch (type) {
561                    case METADATA_TYPE_BITMAP:
562                        MediaMetadataCompatApi21.Builder.putBitmap(builderObj, key,
563                                getBitmap(key));
564                        break;
565                    case METADATA_TYPE_LONG:
566                        MediaMetadataCompatApi21.Builder.putLong(builderObj, key,
567                                getLong(key));
568                        break;
569                    case METADATA_TYPE_RATING:
570                        MediaMetadataCompatApi21.Builder.putRating(builderObj, key,
571                                getRating(key).getRating());
572                        break;
573                    case METADATA_TYPE_TEXT:
574                        MediaMetadataCompatApi21.Builder.putText(builderObj, key,
575                                getText(key));
576                        break;
577                }
578            }
579        }
580        mMetadataObj = MediaMetadataCompatApi21.Builder.build(builderObj);
581        return mMetadataObj;
582    }
583
584    public static final Parcelable.Creator<MediaMetadataCompat> CREATOR =
585            new Parcelable.Creator<MediaMetadataCompat>() {
586                @Override
587                public MediaMetadataCompat createFromParcel(Parcel in) {
588                    return new MediaMetadataCompat(in);
589                }
590
591                @Override
592                public MediaMetadataCompat[] newArray(int size) {
593                    return new MediaMetadataCompat[size];
594                }
595            };
596
597    /**
598     * Use to build MediaMetadata objects. The system defined metadata keys must
599     * use the appropriate data type.
600     */
601    public static final class Builder {
602        private final Bundle mBundle;
603
604        /**
605         * Create an empty Builder. Any field that should be included in the
606         * {@link MediaMetadataCompat} must be added.
607         */
608        public Builder() {
609            mBundle = new Bundle();
610        }
611
612        /**
613         * Create a Builder using a {@link MediaMetadataCompat} instance to set the
614         * initial values. All fields in the source metadata will be included in
615         * the new metadata. Fields can be overwritten by adding the same key.
616         *
617         * @param source
618         */
619        public Builder(MediaMetadataCompat source) {
620            mBundle = new Bundle(source.mBundle);
621        }
622
623        /**
624         * Put a CharSequence value into the metadata. Custom keys may be used,
625         * but if the METADATA_KEYs defined in this class are used they may only
626         * be one of the following:
627         * <ul>
628         * <li>{@link #METADATA_KEY_TITLE}</li>
629         * <li>{@link #METADATA_KEY_ARTIST}</li>
630         * <li>{@link #METADATA_KEY_ALBUM}</li>
631         * <li>{@link #METADATA_KEY_AUTHOR}</li>
632         * <li>{@link #METADATA_KEY_WRITER}</li>
633         * <li>{@link #METADATA_KEY_COMPOSER}</li>
634         * <li>{@link #METADATA_KEY_DATE}</li>
635         * <li>{@link #METADATA_KEY_GENRE}</li>
636         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
637         * <li>{@link #METADATA_KEY_ART_URI}</li>
638         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
639         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
640         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
641         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
642         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
643         * </ul>
644         *
645         * @param key The key for referencing this value
646         * @param value The CharSequence value to store
647         * @return The Builder to allow chaining
648         */
649        public Builder putText(@TextKey String key, CharSequence value) {
650            if (METADATA_KEYS_TYPE.containsKey(key)) {
651                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
652                    throw new IllegalArgumentException("The " + key
653                            + " key cannot be used to put a CharSequence");
654                }
655            }
656            mBundle.putCharSequence(key, value);
657            return this;
658        }
659
660        /**
661         * Put a String value into the metadata. Custom keys may be used, but if
662         * the METADATA_KEYs defined in this class are used they may only be one
663         * of the following:
664         * <ul>
665         * <li>{@link #METADATA_KEY_TITLE}</li>
666         * <li>{@link #METADATA_KEY_ARTIST}</li>
667         * <li>{@link #METADATA_KEY_ALBUM}</li>
668         * <li>{@link #METADATA_KEY_AUTHOR}</li>
669         * <li>{@link #METADATA_KEY_WRITER}</li>
670         * <li>{@link #METADATA_KEY_COMPOSER}</li>
671         * <li>{@link #METADATA_KEY_DATE}</li>
672         * <li>{@link #METADATA_KEY_GENRE}</li>
673         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
674         * <li>{@link #METADATA_KEY_ART_URI}</li>
675         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
676         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
677         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
678         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
679         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
680         * </ul>
681         *
682         * @param key The key for referencing this value
683         * @param value The String value to store
684         * @return The Builder to allow chaining
685         */
686        public Builder putString(@TextKey String key, String value) {
687            if (METADATA_KEYS_TYPE.containsKey(key)) {
688                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
689                    throw new IllegalArgumentException("The " + key
690                            + " key cannot be used to put a String");
691                }
692            }
693            mBundle.putCharSequence(key, value);
694            return this;
695        }
696
697        /**
698         * Put a long value into the metadata. Custom keys may be used, but if
699         * the METADATA_KEYs defined in this class are used they may only be one
700         * of the following:
701         * <ul>
702         * <li>{@link #METADATA_KEY_DURATION}</li>
703         * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
704         * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
705         * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
706         * <li>{@link #METADATA_KEY_YEAR}</li>
707         * </ul>
708         *
709         * @param key The key for referencing this value
710         * @param value The String value to store
711         * @return The Builder to allow chaining
712         */
713        public Builder putLong(@LongKey String key, long value) {
714            if (METADATA_KEYS_TYPE.containsKey(key)) {
715                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
716                    throw new IllegalArgumentException("The " + key
717                            + " key cannot be used to put a long");
718                }
719            }
720            mBundle.putLong(key, value);
721            return this;
722        }
723
724        /**
725         * Put a {@link RatingCompat} into the metadata. Custom keys may be used, but
726         * if the METADATA_KEYs defined in this class are used they may only be
727         * one of the following:
728         * <ul>
729         * <li>{@link #METADATA_KEY_RATING}</li>
730         * <li>{@link #METADATA_KEY_USER_RATING}</li>
731         * </ul>
732         *
733         * @param key The key for referencing this value
734         * @param value The String value to store
735         * @return The Builder to allow chaining
736         */
737        public Builder putRating(@RatingKey String key, RatingCompat value) {
738            if (METADATA_KEYS_TYPE.containsKey(key)) {
739                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
740                    throw new IllegalArgumentException("The " + key
741                            + " key cannot be used to put a Rating");
742                }
743            }
744            mBundle.putParcelable(key, value);
745            return this;
746        }
747
748        /**
749         * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
750         * if the METADATA_KEYs defined in this class are used they may only be
751         * one of the following:
752         * <ul>
753         * <li>{@link #METADATA_KEY_ART}</li>
754         * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
755         * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
756         * </ul>
757         *
758         * @param key The key for referencing this value
759         * @param value The Bitmap to store
760         * @return The Builder to allow chaining
761         */
762        public Builder putBitmap(@BitmapKey String key, Bitmap value) {
763            if (METADATA_KEYS_TYPE.containsKey(key)) {
764                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
765                    throw new IllegalArgumentException("The " + key
766                            + " key cannot be used to put a Bitmap");
767                }
768            }
769            mBundle.putParcelable(key, value);
770            return this;
771        }
772
773        /**
774         * Creates a {@link MediaMetadataCompat} instance with the specified fields.
775         *
776         * @return The new MediaMetadata instance
777         */
778        public MediaMetadataCompat build() {
779            return new MediaMetadataCompat(mBundle);
780        }
781    }
782
783}
784