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