1/*
2 * Copyright 2018 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
17package androidx.media;
18
19import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_DESCRIPTION;
20import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_ICON;
21import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_ICON_URI;
22import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_SUBTITLE;
23import static androidx.media.MediaMetadata2.METADATA_KEY_DISPLAY_TITLE;
24import static androidx.media.MediaMetadata2.METADATA_KEY_EXTRAS;
25import static androidx.media.MediaMetadata2.METADATA_KEY_MEDIA_ID;
26import static androidx.media.MediaMetadata2.METADATA_KEY_MEDIA_URI;
27import static androidx.media.MediaMetadata2.METADATA_KEY_TITLE;
28
29import android.graphics.Bitmap;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Parcelable;
33import android.support.v4.media.MediaBrowserCompat.MediaItem;
34import android.support.v4.media.MediaDescriptionCompat;
35import android.support.v4.media.MediaMetadataCompat;
36import android.support.v4.media.RatingCompat;
37import android.support.v4.media.session.PlaybackStateCompat;
38
39import androidx.media.MediaSession2.CommandButton;
40
41import java.util.ArrayList;
42import java.util.List;
43
44class MediaUtils2 {
45    static final String TAG = "MediaUtils2";
46
47    private MediaUtils2() {
48    }
49
50    /**
51     * Creates a {@link MediaItem} from the {@link MediaItem2}.
52     *
53     * @param item2 an item.
54     * @return The newly created media item.
55     */
56    static MediaItem createMediaItem(MediaItem2 item2) {
57        if (item2 == null) {
58            return null;
59        }
60        MediaDescriptionCompat descCompat;
61
62        MediaMetadata2 metadata = item2.getMetadata();
63        if (metadata == null) {
64            descCompat = new MediaDescriptionCompat.Builder()
65                    .setMediaId(item2.getMediaId())
66                    .build();
67        } else {
68            MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder()
69                    .setMediaId(item2.getMediaId())
70                    .setSubtitle(metadata.getText(METADATA_KEY_DISPLAY_SUBTITLE))
71                    .setDescription(metadata.getText(METADATA_KEY_DISPLAY_DESCRIPTION))
72                    .setIconBitmap(metadata.getBitmap(METADATA_KEY_DISPLAY_ICON))
73                    .setExtras(metadata.getExtras());
74
75            String title = metadata.getString(METADATA_KEY_TITLE);
76            if (title != null) {
77                builder.setTitle(title);
78            } else {
79                builder.setTitle(metadata.getString(METADATA_KEY_DISPLAY_TITLE));
80            }
81
82            String displayIconUri = metadata.getString(METADATA_KEY_DISPLAY_ICON_URI);
83            if (displayIconUri != null) {
84                builder.setIconUri(Uri.parse(displayIconUri));
85            }
86
87            String mediaUri = metadata.getString(METADATA_KEY_MEDIA_URI);
88            if (mediaUri != null) {
89                builder.setMediaUri(Uri.parse(mediaUri));
90            }
91
92            descCompat = builder.build();
93        }
94        return new MediaItem(descCompat, item2.getFlags());
95    }
96
97    /**
98     * Creates a {@link MediaItem2} from the {@link MediaItem}.
99     *
100     * @param item an item.
101     * @return The newly created media item.
102     */
103    static MediaItem2 createMediaItem2(MediaItem item) {
104        if (item == null || item.getMediaId() == null) {
105            return null;
106        }
107
108        MediaMetadata2 metadata2 = createMediaMetadata2(item.getDescription());
109        return new MediaItem2.Builder(item.getFlags())
110                .setMediaId(item.getMediaId())
111                .setMetadata(metadata2)
112                .build();
113    }
114
115    static List<MediaItem> fromMediaItem2List(List<MediaItem2> items) {
116        if (items == null) {
117            return null;
118        }
119        List<MediaItem> result = new ArrayList<>();
120        for (int i = 0; i < items.size(); i++) {
121            result.add(createMediaItem(items.get(i)));
122        }
123        return result;
124    }
125
126    static List<MediaItem2> toMediaItem2List(List<MediaItem> items) {
127        if (items == null) {
128            return null;
129        }
130        List<MediaItem2> result = new ArrayList<>();
131        for (int i = 0; i < items.size(); i++) {
132            result.add(createMediaItem2(items.get(i)));
133        }
134        return result;
135    }
136
137    /**
138     * Creates a {@link MediaMetadata2} from the {@link MediaDescriptionCompat}.
139     *
140     * @param descCompat A {@link MediaDescriptionCompat} object.
141     * @return The newly created {@link MediaMetadata2} object.
142     */
143    static MediaMetadata2 createMediaMetadata2(MediaDescriptionCompat descCompat) {
144        if (descCompat == null) {
145            return null;
146        }
147
148        MediaMetadata2.Builder metadata2Builder = new MediaMetadata2.Builder();
149        metadata2Builder.putString(METADATA_KEY_MEDIA_ID, descCompat.getMediaId());
150
151        CharSequence title = descCompat.getTitle();
152        if (title != null) {
153            metadata2Builder.putText(METADATA_KEY_DISPLAY_TITLE, title);
154        }
155
156        CharSequence description = descCompat.getDescription();
157        if (description != null) {
158            metadata2Builder.putText(METADATA_KEY_DISPLAY_DESCRIPTION, descCompat.getDescription());
159        }
160
161        CharSequence subtitle = descCompat.getSubtitle();
162        if (subtitle != null) {
163            metadata2Builder.putText(METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
164        }
165
166        Bitmap icon = descCompat.getIconBitmap();
167        if (icon != null) {
168            metadata2Builder.putBitmap(METADATA_KEY_DISPLAY_ICON, icon);
169        }
170
171        Uri iconUri = descCompat.getIconUri();
172        if (iconUri != null) {
173            metadata2Builder.putText(METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString());
174        }
175
176        Bundle bundle = descCompat.getExtras();
177        if (bundle != null) {
178            metadata2Builder.setExtras(descCompat.getExtras());
179        }
180
181        Uri mediaUri = descCompat.getMediaUri();
182        if (mediaUri != null) {
183            metadata2Builder.putText(METADATA_KEY_MEDIA_URI, mediaUri.toString());
184        }
185
186        return metadata2Builder.build();
187    }
188
189    /**
190     * Creates a {@link MediaMetadata2} from the {@link MediaMetadataCompat}.
191     *
192     * @param metadataCompat A {@link MediaMetadataCompat} object.
193     * @return The newly created {@link MediaMetadata2} object.
194     */
195    MediaMetadata2 createMediaMetadata2(MediaMetadataCompat metadataCompat) {
196        if (metadataCompat == null) {
197            return null;
198        }
199        return new MediaMetadata2(metadataCompat.getBundle());
200    }
201
202    /**
203     * Creates a {@link MediaMetadataCompat} from the {@link MediaMetadata2}.
204     *
205     * @param metadata2 A {@link MediaMetadata2} object.
206     * @return The newly created {@link MediaMetadataCompat} object.
207     */
208    MediaMetadataCompat createMediaMetadataCompat(MediaMetadata2 metadata2) {
209        if (metadata2 == null) {
210            return null;
211        }
212
213        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
214
215        List<String> skippedKeys = new ArrayList<>();
216        Bundle bundle = metadata2.toBundle();
217        for (String key : bundle.keySet()) {
218            Object value = bundle.get(key);
219            if (value instanceof CharSequence) {
220                builder.putText(key, (CharSequence) value);
221            } else if (value instanceof Rating2) {
222                builder.putRating(key, createRatingCompat((Rating2) value));
223            } else if (value instanceof Bitmap) {
224                builder.putBitmap(key, (Bitmap) value);
225            } else if (value instanceof Long) {
226                builder.putLong(key, (Long) value);
227            } else {
228                // There is no 'float' or 'bundle' type in MediaMetadataCompat.
229                skippedKeys.add(key);
230            }
231        }
232
233        MediaMetadataCompat result = builder.build();
234        for (String key : skippedKeys) {
235            Object value = bundle.get(key);
236            if (value instanceof Float) {
237                // Compatibility for MediaMetadata2.Builder.putFloat()
238                result.getBundle().putFloat(key, (Float) value);
239            } else if (METADATA_KEY_EXTRAS.equals(value)) {
240                // Compatibility for MediaMetadata2.Builder.setExtras()
241                result.getBundle().putBundle(key, (Bundle) value);
242            }
243        }
244        return result;
245    }
246
247    /**
248     * Creates a {@link Rating2} from the {@link RatingCompat}.
249     *
250     * @param ratingCompat A {@link RatingCompat} object.
251     * @return The newly created {@link Rating2} object.
252     */
253    Rating2 createRating2(RatingCompat ratingCompat) {
254        if (ratingCompat == null) {
255            return null;
256        }
257        if (!ratingCompat.isRated()) {
258            return Rating2.newUnratedRating(ratingCompat.getRatingStyle());
259        }
260
261        switch (ratingCompat.getRatingStyle()) {
262            case RatingCompat.RATING_3_STARS:
263            case RatingCompat.RATING_4_STARS:
264            case RatingCompat.RATING_5_STARS:
265                return Rating2.newStarRating(
266                        ratingCompat.getRatingStyle(), ratingCompat.getStarRating());
267            case RatingCompat.RATING_HEART:
268                return Rating2.newHeartRating(ratingCompat.hasHeart());
269            case RatingCompat.RATING_THUMB_UP_DOWN:
270                return Rating2.newThumbRating(ratingCompat.isThumbUp());
271            case RatingCompat.RATING_PERCENTAGE:
272                return Rating2.newPercentageRating(ratingCompat.getPercentRating());
273            default:
274                return null;
275        }
276    }
277
278    /**
279     * Creates a {@link RatingCompat} from the {@link Rating2}.
280     *
281     * @param rating2 A {@link Rating2} object.
282     * @return The newly created {@link RatingCompat} object.
283     */
284    RatingCompat createRatingCompat(Rating2 rating2) {
285        if (rating2 == null) {
286            return null;
287        }
288        if (!rating2.isRated()) {
289            return RatingCompat.newUnratedRating(rating2.getRatingStyle());
290        }
291
292        switch (rating2.getRatingStyle()) {
293            case Rating2.RATING_3_STARS:
294            case Rating2.RATING_4_STARS:
295            case Rating2.RATING_5_STARS:
296                return RatingCompat.newStarRating(
297                        rating2.getRatingStyle(), rating2.getStarRating());
298            case Rating2.RATING_HEART:
299                return RatingCompat.newHeartRating(rating2.hasHeart());
300            case Rating2.RATING_THUMB_UP_DOWN:
301                return RatingCompat.newThumbRating(rating2.isThumbUp());
302            case Rating2.RATING_PERCENTAGE:
303                return RatingCompat.newPercentageRating(rating2.getPercentRating());
304            default:
305                return null;
306        }
307    }
308
309    static Parcelable[] toMediaItem2ParcelableArray(List<MediaItem2> playlist) {
310        if (playlist == null) {
311            return null;
312        }
313        List<Parcelable> parcelableList = new ArrayList<>();
314        for (int i = 0; i < playlist.size(); i++) {
315            final MediaItem2 item = playlist.get(i);
316            if (item != null) {
317                final Parcelable itemBundle = item.toBundle();
318                if (itemBundle != null) {
319                    parcelableList.add(itemBundle);
320                }
321            }
322        }
323        return parcelableList.toArray(new Parcelable[0]);
324    }
325
326    static List<MediaItem2> fromMediaItem2ParcelableArray(Parcelable[] itemParcelableList) {
327        List<MediaItem2> playlist = new ArrayList<>();
328        if (itemParcelableList != null) {
329            for (int i = 0; i < itemParcelableList.length; i++) {
330                if (!(itemParcelableList[i] instanceof Bundle)) {
331                    continue;
332                }
333                MediaItem2 item = MediaItem2.fromBundle((Bundle) itemParcelableList[i]);
334                if (item != null) {
335                    playlist.add(item);
336                }
337            }
338        }
339        return playlist;
340    }
341
342    static Parcelable[] toCommandButtonParcelableArray(List<CommandButton> layout) {
343        if (layout == null) {
344            return null;
345        }
346        List<Bundle> layoutBundles = new ArrayList<>();
347        for (int i = 0; i < layout.size(); i++) {
348            Bundle bundle = layout.get(i).toBundle();
349            if (bundle != null) {
350                layoutBundles.add(bundle);
351            }
352        }
353        return layoutBundles.toArray(new Parcelable[0]);
354    }
355
356    static List<CommandButton> fromCommandButtonParcelableArray(Parcelable[] list) {
357        List<CommandButton> layout = new ArrayList<>();
358        if (layout != null) {
359            for (int i = 0; i < list.length; i++) {
360                if (!(list[i] instanceof Bundle)) {
361                    continue;
362                }
363                CommandButton button = CommandButton.fromBundle((Bundle) list[i]);
364                if (button != null) {
365                    layout.add(button);
366                }
367            }
368        }
369        return layout;
370    }
371
372    static List<Bundle> toBundleList(Parcelable[] array) {
373        if (array == null) {
374            return null;
375        }
376        List<Bundle> bundleList = new ArrayList<>();
377        for (Parcelable p : array) {
378            bundleList.add((Bundle) p);
379        }
380        return bundleList;
381    }
382
383    static int createPlaybackStateCompatState(int playerState, int bufferingState) {
384        switch (playerState) {
385            case MediaPlayerInterface.PLAYER_STATE_PLAYING:
386                switch (bufferingState) {
387                    case MediaPlayerInterface.BUFFERING_STATE_BUFFERING_AND_STARVED:
388                        return PlaybackStateCompat.STATE_BUFFERING;
389                }
390                return PlaybackStateCompat.STATE_PLAYING;
391            case MediaPlayerInterface.PLAYER_STATE_PAUSED:
392                return PlaybackStateCompat.STATE_PAUSED;
393            case MediaPlayerInterface.PLAYER_STATE_IDLE:
394                return PlaybackStateCompat.STATE_NONE;
395            case MediaPlayerInterface.PLAYER_STATE_ERROR:
396                return PlaybackStateCompat.STATE_ERROR;
397        }
398        // For unknown value
399        return PlaybackStateCompat.STATE_ERROR;
400    }
401
402    static int toPlayerState(int playbackStateCompatState) {
403        switch (playbackStateCompatState) {
404            case PlaybackStateCompat.STATE_ERROR:
405                return MediaPlayerInterface.PLAYER_STATE_ERROR;
406            case PlaybackStateCompat.STATE_NONE:
407                return MediaPlayerInterface.PLAYER_STATE_IDLE;
408            case PlaybackStateCompat.STATE_PAUSED:
409            case PlaybackStateCompat.STATE_STOPPED:
410            case PlaybackStateCompat.STATE_BUFFERING: // means paused for buffering.
411                return MediaPlayerInterface.PLAYER_STATE_PAUSED;
412            case PlaybackStateCompat.STATE_FAST_FORWARDING:
413            case PlaybackStateCompat.STATE_PLAYING:
414            case PlaybackStateCompat.STATE_REWINDING:
415            case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
416            case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
417            case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
418            case PlaybackStateCompat.STATE_CONNECTING: // Note: there's no perfect match for this.
419                return MediaPlayerInterface.PLAYER_STATE_PLAYING;
420        }
421        return MediaPlayerInterface.PLAYER_STATE_ERROR;
422    }
423
424    static boolean isDefaultLibraryRootHint(Bundle bundle) {
425        return bundle != null && bundle.getBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, false);
426    }
427
428    static Bundle createBundle(Bundle bundle) {
429        return (bundle == null) ? new Bundle() : new Bundle(bundle);
430    }
431}
432