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.Nullable;
25import android.support.annotation.RestrictTo;
26import android.text.TextUtils;
27
28import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
29
30/**
31 * A simple set of metadata for a media item suitable for display. This can be
32 * created using the Builder or retrieved from existing metadata using
33 * {@link MediaMetadataCompat#getDescription()}.
34 */
35public final class MediaDescriptionCompat implements Parcelable {
36    /**
37     * Used as a long extra field to indicate the bluetooth folder type of the media item as
38     * specified in the section 6.10.2.2 of the Bluetooth AVRCP 1.5. This is valid only for
39     * {@link MediaBrowserCompat.MediaItem} with
40     * {@link MediaBrowserCompat.MediaItem#FLAG_BROWSABLE}. The value should be one of the
41     * following:
42     * <ul>
43     * <li>{@link #BT_FOLDER_TYPE_MIXED}</li>
44     * <li>{@link #BT_FOLDER_TYPE_TITLES}</li>
45     * <li>{@link #BT_FOLDER_TYPE_ALBUMS}</li>
46     * <li>{@link #BT_FOLDER_TYPE_ARTISTS}</li>
47     * <li>{@link #BT_FOLDER_TYPE_GENRES}</li>
48     * <li>{@link #BT_FOLDER_TYPE_PLAYLISTS}</li>
49     * <li>{@link #BT_FOLDER_TYPE_YEARS}</li>
50     * </ul>
51     *
52     * @see #getExtras()
53     */
54    public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
55
56    /**
57     * The type of folder that is unknown or contains media elements of mixed types as specified in
58     * the section 6.10.2.2 of the Bluetooth AVRCP 1.5.
59     */
60    public static final long BT_FOLDER_TYPE_MIXED = 0;
61
62    /**
63     * The type of folder that contains media elements only as specified in the section 6.10.2.2 of
64     * the Bluetooth AVRCP 1.5.
65     */
66    public static final long BT_FOLDER_TYPE_TITLES = 1;
67
68    /**
69     * The type of folder that contains folders categorized by album as specified in the section
70     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
71     */
72    public static final long BT_FOLDER_TYPE_ALBUMS = 2;
73
74    /**
75     * The type of folder that contains folders categorized by artist as specified in the section
76     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
77     */
78    public static final long BT_FOLDER_TYPE_ARTISTS = 3;
79
80    /**
81     * The type of folder that contains folders categorized by genre as specified in the section
82     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
83     */
84    public static final long BT_FOLDER_TYPE_GENRES = 4;
85
86    /**
87     * The type of folder that contains folders categorized by playlist as specified in the section
88     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
89     */
90    public static final long BT_FOLDER_TYPE_PLAYLISTS = 5;
91
92    /**
93     * The type of folder that contains folders categorized by year as specified in the section
94     * 6.10.2.2 of the Bluetooth AVRCP 1.5.
95     */
96    public static final long BT_FOLDER_TYPE_YEARS = 6;
97
98    /**
99     * Custom key to store a media URI on API 21-22 devices (before it became part of the
100     * framework class) when parceling/converting to and from framework objects.
101     *
102     * @hide
103     */
104    @RestrictTo(GROUP_ID)
105    public static final String DESCRIPTION_KEY_MEDIA_URI =
106            "android.support.v4.media.description.MEDIA_URI";
107    /**
108     * Custom key to store whether the original Bundle provided by the developer was null
109     *
110     * @hide
111     */
112    @RestrictTo(GROUP_ID)
113    public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG =
114            "android.support.v4.media.description.NULL_BUNDLE_FLAG";
115    /**
116     * A unique persistent id for the content or null.
117     */
118    private final String mMediaId;
119    /**
120     * A primary title suitable for display or null.
121     */
122    private final CharSequence mTitle;
123    /**
124     * A subtitle suitable for display or null.
125     */
126    private final CharSequence mSubtitle;
127    /**
128     * A description suitable for display or null.
129     */
130    private final CharSequence mDescription;
131    /**
132     * A bitmap icon suitable for display or null.
133     */
134    private final Bitmap mIcon;
135    /**
136     * A Uri for an icon suitable for display or null.
137     */
138    private final Uri mIconUri;
139    /**
140     * Extras for opaque use by apps/system.
141     */
142    private final Bundle mExtras;
143    /**
144     * A Uri to identify this content.
145     */
146    private final Uri mMediaUri;
147
148    /**
149     * A cached copy of the equivalent framework object.
150     */
151    private Object mDescriptionObj;
152
153    MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle,
154            CharSequence description, Bitmap icon, Uri iconUri, Bundle extras, Uri mediaUri) {
155        mMediaId = mediaId;
156        mTitle = title;
157        mSubtitle = subtitle;
158        mDescription = description;
159        mIcon = icon;
160        mIconUri = iconUri;
161        mExtras = extras;
162        mMediaUri = mediaUri;
163    }
164
165    MediaDescriptionCompat(Parcel in) {
166        mMediaId = in.readString();
167        mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
168        mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
169        mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
170        mIcon = in.readParcelable(null);
171        mIconUri = in.readParcelable(null);
172        mExtras = in.readBundle();
173        mMediaUri = in.readParcelable(null);
174    }
175
176    /**
177     * Returns the media id or null. See
178     * {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}.
179     */
180    @Nullable
181    public String getMediaId() {
182        return mMediaId;
183    }
184
185    /**
186     * Returns a title suitable for display or null.
187     *
188     * @return A title or null.
189     */
190    @Nullable
191    public CharSequence getTitle() {
192        return mTitle;
193    }
194
195    /**
196     * Returns a subtitle suitable for display or null.
197     *
198     * @return A subtitle or null.
199     */
200    @Nullable
201    public CharSequence getSubtitle() {
202        return mSubtitle;
203    }
204
205    /**
206     * Returns a description suitable for display or null.
207     *
208     * @return A description or null.
209     */
210    @Nullable
211    public CharSequence getDescription() {
212        return mDescription;
213    }
214
215    /**
216     * Returns a bitmap icon suitable for display or null.
217     *
218     * @return An icon or null.
219     */
220    @Nullable
221    public Bitmap getIconBitmap() {
222        return mIcon;
223    }
224
225    /**
226     * Returns a Uri for an icon suitable for display or null.
227     *
228     * @return An icon uri or null.
229     */
230    @Nullable
231    public Uri getIconUri() {
232        return mIconUri;
233    }
234
235    /**
236     * Returns any extras that were added to the description.
237     *
238     * @return A bundle of extras or null.
239     */
240    @Nullable
241    public Bundle getExtras() {
242        return mExtras;
243    }
244
245    /**
246     * Returns a Uri representing this content or null.
247     *
248     * @return A media Uri or null.
249     */
250    @Nullable
251    public Uri getMediaUri() {
252        return mMediaUri;
253    }
254
255    @Override
256    public int describeContents() {
257        return 0;
258    }
259
260    @Override
261    public void writeToParcel(Parcel dest, int flags) {
262        if (Build.VERSION.SDK_INT < 21) {
263            dest.writeString(mMediaId);
264            TextUtils.writeToParcel(mTitle, dest, flags);
265            TextUtils.writeToParcel(mSubtitle, dest, flags);
266            TextUtils.writeToParcel(mDescription, dest, flags);
267            dest.writeParcelable(mIcon, flags);
268            dest.writeParcelable(mIconUri, flags);
269            dest.writeBundle(mExtras);
270            dest.writeParcelable(mMediaUri, flags);
271        } else {
272            MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags);
273        }
274    }
275
276    @Override
277    public String toString() {
278        return mTitle + ", " + mSubtitle + ", " + mDescription;
279    }
280
281    /**
282     * Gets the underlying framework {@link android.media.MediaDescription}
283     * object.
284     * <p>
285     * This method is only supported on
286     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
287     * </p>
288     *
289     * @return An equivalent {@link android.media.MediaDescription} object, or
290     *         null if none.
291     */
292    public Object getMediaDescription() {
293        if (mDescriptionObj != null || Build.VERSION.SDK_INT < 21) {
294            return mDescriptionObj;
295        }
296        Object bob = MediaDescriptionCompatApi21.Builder.newInstance();
297        MediaDescriptionCompatApi21.Builder.setMediaId(bob, mMediaId);
298        MediaDescriptionCompatApi21.Builder.setTitle(bob, mTitle);
299        MediaDescriptionCompatApi21.Builder.setSubtitle(bob, mSubtitle);
300        MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription);
301        MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon);
302        MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri);
303        // Media URI was not added until API 23, so add it to the Bundle of extras to
304        // ensure the data is not lost - this ensures that
305        // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns
306        // an equivalent MediaDescriptionCompat on all API levels
307        Bundle extras = mExtras;
308        if (Build.VERSION.SDK_INT < 23 && mMediaUri != null) {
309            if (extras == null) {
310                extras = new Bundle();
311                extras.putBoolean(DESCRIPTION_KEY_NULL_BUNDLE_FLAG, true);
312            }
313            extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
314        }
315        MediaDescriptionCompatApi21.Builder.setExtras(bob, extras);
316        if (Build.VERSION.SDK_INT >= 23) {
317            MediaDescriptionCompatApi23.Builder.setMediaUri(bob, mMediaUri);
318        }
319        mDescriptionObj = MediaDescriptionCompatApi21.Builder.build(bob);
320
321        return mDescriptionObj;
322    }
323
324    /**
325     * Creates an instance from a framework
326     * {@link android.media.MediaDescription} object.
327     * <p>
328     * This method is only supported on API 21+.
329     * </p>
330     *
331     * @param descriptionObj A {@link android.media.MediaDescription} object, or
332     *            null if none.
333     * @return An equivalent {@link MediaMetadataCompat} object, or null if
334     *         none.
335     */
336    public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
337        if (descriptionObj == null || Build.VERSION.SDK_INT < 21) {
338            return null;
339        }
340
341        Builder bob = new Builder();
342        bob.setMediaId(MediaDescriptionCompatApi21.getMediaId(descriptionObj));
343        bob.setTitle(MediaDescriptionCompatApi21.getTitle(descriptionObj));
344        bob.setSubtitle(MediaDescriptionCompatApi21.getSubtitle(descriptionObj));
345        bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj));
346        bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj));
347        bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj));
348        Bundle extras = MediaDescriptionCompatApi21.getExtras(descriptionObj);
349        Uri mediaUri = extras == null ? null :
350                (Uri) extras.getParcelable(DESCRIPTION_KEY_MEDIA_URI);
351        if (mediaUri != null) {
352            if (extras.containsKey(DESCRIPTION_KEY_NULL_BUNDLE_FLAG) && extras.size() == 2) {
353                // The extras were only created for the media URI, so we set it back to null to
354                // ensure mediaDescriptionCompat.getExtras() equals
355                // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)).getExtras()
356                extras = null;
357            } else {
358                // Remove media URI keys to ensure mediaDescriptionCompat.getExtras().keySet()
359                // equals fromMediaDescription(getMediaDescription(mediaDescriptionCompat))
360                // .getExtras().keySet()
361                extras.remove(DESCRIPTION_KEY_MEDIA_URI);
362                extras.remove(DESCRIPTION_KEY_NULL_BUNDLE_FLAG);
363            }
364        }
365        bob.setExtras(extras);
366        if (mediaUri != null) {
367            bob.setMediaUri(mediaUri);
368        } else if (Build.VERSION.SDK_INT >= 23) {
369            bob.setMediaUri(MediaDescriptionCompatApi23.getMediaUri(descriptionObj));
370        }
371        MediaDescriptionCompat descriptionCompat = bob.build();
372        descriptionCompat.mDescriptionObj = descriptionObj;
373
374        return descriptionCompat;
375    }
376
377    public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR =
378            new Parcelable.Creator<MediaDescriptionCompat>() {
379            @Override
380                public MediaDescriptionCompat createFromParcel(Parcel in) {
381                    if (Build.VERSION.SDK_INT < 21) {
382                        return new MediaDescriptionCompat(in);
383                    } else {
384                        return fromMediaDescription(MediaDescriptionCompatApi21.fromParcel(in));
385                    }
386                }
387
388            @Override
389                public MediaDescriptionCompat[] newArray(int size) {
390                    return new MediaDescriptionCompat[size];
391                }
392            };
393
394    /**
395     * Builder for {@link MediaDescriptionCompat} objects.
396     */
397    public static final class Builder {
398        private String mMediaId;
399        private CharSequence mTitle;
400        private CharSequence mSubtitle;
401        private CharSequence mDescription;
402        private Bitmap mIcon;
403        private Uri mIconUri;
404        private Bundle mExtras;
405        private Uri mMediaUri;
406
407        /**
408         * Creates an initially empty builder.
409         */
410        public Builder() {
411        }
412
413        /**
414         * Sets the media id.
415         *
416         * @param mediaId The unique id for the item or null.
417         * @return this
418         */
419        public Builder setMediaId(@Nullable String mediaId) {
420            mMediaId = mediaId;
421            return this;
422        }
423
424        /**
425         * Sets the title.
426         *
427         * @param title A title suitable for display to the user or null.
428         * @return this
429         */
430        public Builder setTitle(@Nullable CharSequence title) {
431            mTitle = title;
432            return this;
433        }
434
435        /**
436         * Sets the subtitle.
437         *
438         * @param subtitle A subtitle suitable for display to the user or null.
439         * @return this
440         */
441        public Builder setSubtitle(@Nullable CharSequence subtitle) {
442            mSubtitle = subtitle;
443            return this;
444        }
445
446        /**
447         * Sets the description.
448         *
449         * @param description A description suitable for display to the user or
450         *            null.
451         * @return this
452         */
453        public Builder setDescription(@Nullable CharSequence description) {
454            mDescription = description;
455            return this;
456        }
457
458        /**
459         * Sets the icon.
460         *
461         * @param icon A {@link Bitmap} icon suitable for display to the user or
462         *            null.
463         * @return this
464         */
465        public Builder setIconBitmap(@Nullable Bitmap icon) {
466            mIcon = icon;
467            return this;
468        }
469
470        /**
471         * Sets the icon uri.
472         *
473         * @param iconUri A {@link Uri} for an icon suitable for display to the
474         *            user or null.
475         * @return this
476         */
477        public Builder setIconUri(@Nullable Uri iconUri) {
478            mIconUri = iconUri;
479            return this;
480        }
481
482        /**
483         * Sets a bundle of extras.
484         *
485         * @param extras The extras to include with this description or null.
486         * @return this
487         */
488        public Builder setExtras(@Nullable Bundle extras) {
489            mExtras = extras;
490            return this;
491        }
492
493        /**
494         * Sets the media uri.
495         *
496         * @param mediaUri The content's {@link Uri} for the item or null.
497         * @return this
498         */
499        public Builder setMediaUri(@Nullable Uri mediaUri) {
500            mMediaUri = mediaUri;
501            return this;
502        }
503
504        /**
505         * Creates a {@link MediaDescriptionCompat} instance with the specified
506         * fields.
507         *
508         * @return A MediaDescriptionCompat instance.
509         */
510        public MediaDescriptionCompat build() {
511            return new MediaDescriptionCompat(mMediaId, mTitle, mSubtitle, mDescription, mIcon,
512                    mIconUri, mExtras, mMediaUri);
513        }
514    }
515}
516