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