1 /*
2  * Copyright 2019 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 
17 package com.android.car.media.testmediaapp;
18 
19 import androidx.annotation.Nullable;
20 
21 import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
22 import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
23 import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID;
24 
25 import android.os.Bundle;
26 import android.support.v4.media.MediaBrowserCompat.MediaItem;
27 import android.support.v4.media.MediaDescriptionCompat;
28 import android.support.v4.media.MediaMetadataCompat;
29 import android.support.v4.media.session.MediaSessionCompat;
30 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.List;
35 
36 /** Our internal representation of media items. */
37 public class TmaMediaItem {
38 
39     private static final String CUSTOM_ACTION_PREFIX = "com.android.car.media.testmediaapp.";
40 
41     /** The name of each entry is the value used in the json file. */
42     public enum ContentStyle {
43         NONE (0),
44         LIST (MediaKeys.CONTENT_STYLE_LIST_ITEM_HINT_VALUE),
45         GRID (MediaKeys.CONTENT_STYLE_GRID_ITEM_HINT_VALUE),
46         LIST_CATEGORY(MediaKeys.CONTENT_STYLE_CATEGORY_LIST_ITEM_HINT_VALUE),
47         GRID_CATEGORY(MediaKeys.CONTENT_STYLE_CATEGORY_GRID_ITEM_HINT_VALUE);
48         final int mBundleValue;
ContentStyle(int value)49         ContentStyle(int value) {
50             mBundleValue = value;
51         }
52     }
53 
54     public enum TmaCustomAction {
55         HEART_PLUS_PLUS(CUSTOM_ACTION_PREFIX + "heart_plus_plus", R.string.heart_plus_plus,
56                 R.drawable.ic_heart_plus_plus),
57         HEART_LESS_LESS(CUSTOM_ACTION_PREFIX + "heart_less_less", R.string.heart_less_less,
58                 R.drawable.ic_heart_less_less),
59         REQUEST_LOCATION(CUSTOM_ACTION_PREFIX + "location", R.string.location,
60                 R.drawable.ic_location);
61 
62         final String mId;
63         final int mNameId;
64         final int mIcon;
65 
TmaCustomAction(String id, int name, int icon)66         TmaCustomAction(String id, int name, int icon) {
67             mId = id;
68             mNameId = name;
69             mIcon = icon;
70         }
71     }
72 
73     private final int mFlags;
74     private final MediaMetadataCompat mMediaMetadata;
75     private final ContentStyle mPlayableStyle;
76     private final ContentStyle mBrowsableStyle;
77     private final int mSelfUpdateMs;
78 
79 
80     /** Internally modifiable list (for includes). */
81     private final List<TmaMediaItem> mChildren = new ArrayList<>();
82     /** Internally modifiable list (for includes). */
83     private final List<TmaMediaItem> mPlayableChildren = new ArrayList<>();
84     /** Read only list. */
85     final List<TmaCustomAction> mCustomActions;
86     /** Read only list. Events triggered when starting the playback. */
87     final List<TmaMediaEvent> mMediaEvents;
88     /** References another json file where to get extra children from. */
89     final String mInclude;
90 
91     private @Nullable TmaMediaItem mParent;
92     int mHearts;
93     int mRevealCounter;
94     boolean mIsHidden = false;
95 
96 
TmaMediaItem(int flags, ContentStyle playableStyle, ContentStyle browsableStyle, MediaMetadataCompat metadata, int selfUpdateMs, List<TmaCustomAction> customActions, List<TmaMediaEvent> mediaEvents, List<TmaMediaItem> children, String include)97     public TmaMediaItem(int flags, ContentStyle playableStyle, ContentStyle browsableStyle,
98             MediaMetadataCompat metadata, int selfUpdateMs,
99             List<TmaCustomAction> customActions, List<TmaMediaEvent> mediaEvents,
100             List<TmaMediaItem> children, String include) {
101         mFlags = flags;
102         mPlayableStyle = playableStyle;
103         mBrowsableStyle = browsableStyle;
104         mMediaMetadata = metadata;
105         mSelfUpdateMs = selfUpdateMs;
106         mCustomActions = Collections.unmodifiableList(customActions);
107         mMediaEvents = Collections.unmodifiableList(mediaEvents);
108         mInclude = include;
109         setChildren(children);
110     }
111 
setParent(@ullable TmaMediaItem parent)112     private void setParent(@Nullable TmaMediaItem parent) {
113         mParent = parent;
114     }
115 
getSelfUpdateDelay()116     int getSelfUpdateDelay() {
117         return mSelfUpdateMs;
118     }
119 
getChildren()120     List<TmaMediaItem> getChildren() {
121         return Collections.unmodifiableList(mChildren);
122     }
123 
124     @Nullable
getParent()125     TmaMediaItem getParent() {
126         return mParent;
127     }
128 
129     @Nullable
getPlayableByIndex(long index)130     TmaMediaItem getPlayableByIndex(long index) {
131         if (index < 0 || index >= mPlayableChildren.size()) {
132             return null;
133         }
134         return mPlayableChildren.get((int)index);
135     }
136 
137     @Nullable
getPrevious()138     TmaMediaItem getPrevious() {
139         if (mParent == null) return null;
140         List<TmaMediaItem> queueItems = mParent.mPlayableChildren;
141         int myIndex = queueItems.indexOf(this);
142         return (myIndex > 0) ? queueItems.get(myIndex - 1) : null;
143     }
144 
145     @Nullable
getNext()146     TmaMediaItem getNext() {
147         if (mParent == null) return null;
148         List<TmaMediaItem> queueItems = mParent.mPlayableChildren;
149         int myIndex = queueItems.indexOf(this);
150         return (myIndex < queueItems.size() - 1) ? queueItems.get(myIndex + 1) : null;
151     }
152 
getMediaId()153     String getMediaId() {
154         return mMediaMetadata.getString(METADATA_KEY_MEDIA_ID);
155     }
156 
157     /** Returns -1 if the duration key is unspecified or <= 0. */
getDuration()158     long getDuration() {
159         long result = mMediaMetadata.getLong(METADATA_KEY_DURATION);
160         if (result <= 0) return -1;
161         return result;
162     }
163 
setChildren(List<TmaMediaItem> children)164     void setChildren(List<TmaMediaItem> children) {
165         mChildren.clear();
166         mChildren.addAll(children);
167 
168         List<TmaMediaItem> playableChildren = new ArrayList<>(children.size());
169         for (TmaMediaItem child: mChildren) {
170             child.setParent(this);
171             if ((child.mFlags & FLAG_PLAYABLE) != 0) {
172                 playableChildren.add(child);
173             }
174         }
175         mPlayableChildren.clear();
176         mPlayableChildren.addAll(playableChildren);
177     }
178 
updateSessionMetadata(MediaSessionCompat session)179     void updateSessionMetadata(MediaSessionCompat session) {
180         session.setMetadata(mMediaMetadata);
181     }
182 
toMediaItem()183     MediaItem toMediaItem() {
184         return new MediaItem(buildDescription(), mFlags);
185     }
186 
buildQueue()187     List<QueueItem> buildQueue() {
188         int count = mPlayableChildren.size();
189         List<QueueItem> queue = new ArrayList<>(count);
190         for (int i = 0 ; i < count; i++) {
191             TmaMediaItem child = mPlayableChildren.get(i);
192             queue.add(new QueueItem(child.buildDescription(), i));
193         }
194         return queue;
195     }
196 
197     /** Returns the id of the item in the queue. */
getQueueId()198     long getQueueId() {
199         if (mParent != null) {
200             int index = mParent.mPlayableChildren.indexOf(this);
201             if (index >= 0) return index;
202         }
203         return MediaSessionCompat.QueueItem.UNKNOWN_ID;
204     }
205 
buildDescription()206     private MediaDescriptionCompat buildDescription() {
207 
208         // Use the default media description but add our extras.
209         MediaDescriptionCompat metadataDescription = mMediaMetadata.getDescription();
210 
211         MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
212         bob.setMediaId(metadataDescription.getMediaId());
213         bob.setTitle(metadataDescription.getTitle());
214         bob.setSubtitle(metadataDescription.getSubtitle());
215         bob.setDescription(metadataDescription.getDescription());
216         bob.setIconBitmap(metadataDescription.getIconBitmap());
217         bob.setIconUri(metadataDescription.getIconUri());
218         bob.setMediaUri(metadataDescription.getMediaUri());
219 
220         Bundle extras = new Bundle();
221         if (metadataDescription.getExtras() != null) {
222             extras.putAll(metadataDescription.getExtras());
223         }
224 
225         extras.putInt(MediaKeys.CONTENT_STYLE_PLAYABLE_HINT, mPlayableStyle.mBundleValue);
226         extras.putInt(MediaKeys.CONTENT_STYLE_BROWSABLE_HINT, mBrowsableStyle.mBundleValue);
227 
228         bob.setExtras(extras);
229         return bob.build();
230     }
231 }
232