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