diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-07-04 03:05:40 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-07-04 03:05:40 +0000 |
commit | e7ff57db7f26ec086282b1b66b586fdb9a74fd40 (patch) | |
tree | bb7c0c90f67c9b39f8abf3158a0aaaac93a76799 | |
parent | 03f7c6fe46099d358ff1a7ee4c8d8b23feed330a (diff) | |
parent | a1863280183008deeb1b6ce026a680266f944669 (diff) | |
download | tests-android10-mainline-release.tar.gz |
Snap for 5706892 from a1863280183008deeb1b6ce026a680266f944669 to qt-aml-releaseandroid-mainline-10.0.0_r3android-mainline-10.0.0_r1android10-mainline-release
Change-Id: I6e1d19d001b306d92d6b617b6791a8194e728804
12 files changed, 295 insertions, 55 deletions
diff --git a/TestMediaApp/assets/media_items/advanced.json b/TestMediaApp/assets/media_items/advanced.json new file mode 100644 index 0000000..4cf34ea --- /dev/null +++ b/TestMediaApp/assets/media_items/advanced.json @@ -0,0 +1,29 @@ +{ + "FLAGS": "browsable", + + "METADATA": { + "MEDIA_ID": "advanced", + "DISPLAY_TITLE": "Advanced" + }, + + "CHILDREN": [ + { + "FLAGS": "playable", + "METADATA": { + "MEDIA_ID": "advanced custom_action", + "DISPLAY_TITLE": "Custom Action", + "DURATION": 10000 + }, + "CUSTOM_ACTIONS": ["HEART_LESS_LESS", "HEART_PLUS_PLUS"] + }, + { + "FLAGS": "browsable", + "PLAYABLE_HINT": "GRID", + "METADATA": { + "MEDIA_ID": "advanced art nodes", + "DISPLAY_TITLE": "Album Art" + }, + "INCLUDE":"media_items/album_art/art_nodes.json" + } + ] +}
\ No newline at end of file diff --git a/TestMediaApp/assets/media_items/mixed.json b/TestMediaApp/assets/media_items/mixed.json index 872fa4f..7e04e85 100644 --- a/TestMediaApp/assets/media_items/mixed.json +++ b/TestMediaApp/assets/media_items/mixed.json @@ -36,6 +36,16 @@ { "FLAGS": "browsable", "PLAYABLE_HINT": "LIST", + "BROWSABLE_HINT": "LIST", + "METADATA": { + "MEDIA_ID": "mixed advanced", + "DISPLAY_TITLE": "Advanced" + }, + "INCLUDE":"media_items/advanced.json" + }, + { + "FLAGS": "browsable", + "PLAYABLE_HINT": "LIST", "BROWSABLE_HINT": "GRID", "METADATA": { "MEDIA_ID": "mixed rabbit hole", diff --git a/TestMediaApp/assets/media_items/only_nodes.json b/TestMediaApp/assets/media_items/only_nodes.json index 7150104..4858438 100644 --- a/TestMediaApp/assets/media_items/only_nodes.json +++ b/TestMediaApp/assets/media_items/only_nodes.json @@ -19,12 +19,13 @@ }, { "FLAGS": "browsable", - "PLAYABLE_HINT": "GRID", + "PLAYABLE_HINT": "LIST", + "BROWSABLE_HINT": "LIST", "METADATA": { - "MEDIA_ID": "only_nodes art nodes", - "DISPLAY_TITLE": "Album Art" + "MEDIA_ID": "only_nodes advanced", + "DISPLAY_TITLE": "Advanced" }, - "INCLUDE":"media_items/album_art/art_nodes.json" + "INCLUDE":"media_items/advanced.json" }, { "FLAGS": "browsable", diff --git a/TestMediaApp/res/drawable/ic_heart_less_less.png b/TestMediaApp/res/drawable/ic_heart_less_less.png Binary files differnew file mode 100644 index 0000000..e65feac --- /dev/null +++ b/TestMediaApp/res/drawable/ic_heart_less_less.png diff --git a/TestMediaApp/res/drawable/ic_heart_plus_plus.xml b/TestMediaApp/res/drawable/ic_heart_plus_plus.xml new file mode 100644 index 0000000..447431e --- /dev/null +++ b/TestMediaApp/res/drawable/ic_heart_plus_plus.xml @@ -0,0 +1,13 @@ +<vector android:height="24dp" android:viewportHeight="13.229167" + android:viewportWidth="13.229166" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#00000000" + android:pathData="m6.6643,12.8277c0.7318,-3.7194 6.3472,-4.3109 6.3382,-8.9125 -0.009,-4.6016 -4.5689,-5.0237 -6.4909,0.4739 -1.7711,-5.5585 -6.2755,-4.9643 -6.2231,-0.4533 0.0525,4.511 5.8295,5.1724 6.3758,8.8919z" + android:strokeAlpha="1" android:strokeColor="#000000" + android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="0.26458332"/> + <path android:fillAlpha="1" android:fillColor="#000000" + android:pathData="m10.173,6.7307l0,-1.5351l1.5351,-0l0,-0.4685l-1.5351,-0l0,-1.5351L9.71,3.1919l0,1.5351L8.1749,4.727l0,0.4685L9.71,5.1955l0,1.5351z" + android:strokeColor="#00000000" android:strokeWidth="0.26458332"/> + <path android:fillAlpha="1" android:fillColor="#000000" + android:pathData="m3.7619,6.585l0,-1.5351l1.5351,-0l0,-0.4685L3.7619,4.5813l0,-1.5351L3.2989,3.0462l0,1.5351L1.7637,4.5813l0,0.4685l1.5351,-0l0,1.5351z" + android:strokeColor="#00000000" android:strokeWidth="0.26458332"/> +</vector> diff --git a/TestMediaApp/res/values/strings.xml b/TestMediaApp/res/values/strings.xml index e35fcd4..62ec9e1 100644 --- a/TestMediaApp/res/values/strings.xml +++ b/TestMediaApp/res/values/strings.xml @@ -25,4 +25,8 @@ will truncate it when needed. </string> + <string name="heart_plus_plus" translatable="false">Heart++</string> + + <string name="heart_less_less" translatable="false">Heart--</string> + </resources> diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java index 98a9b6b..3bec7ac 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaEvent.java @@ -102,9 +102,9 @@ public class TmaMediaEvent { PREFS } - public final EventState mState; - public final StateErrorCode mErrorCode; - public final String mErrorMessage; + final EventState mState; + final StateErrorCode mErrorCode; + final String mErrorMessage; final String mActionLabel; final ResolutionIntent mResolutionIntent; /** How long to wait before sending the event to the app. */ diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java index 6b56884..6925588 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaMediaItem.java @@ -16,6 +16,9 @@ package com.android.car.media.testmediaapp; +import androidx.annotation.Nullable; + +import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE; import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION; import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID; @@ -26,9 +29,11 @@ import static com.android.car.media.common.MediaConstants.CONTENT_STYLE_PLAYABLE import android.os.Bundle; import android.support.v4.media.MediaBrowserCompat.MediaItem; +import android.support.v4.media.MediaBrowserCompat.MediaItem.Flags; import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.MediaSessionCompat.QueueItem; import java.util.ArrayList; import java.util.Collections; @@ -37,6 +42,8 @@ import java.util.List; /** Our internal representation of media items. */ public class TmaMediaItem { + private static final String CUSTOM_ACTION_PREFIX = "com.android.car.media.testmediaapp."; + /** The name of each entry is the value used in the json file. */ public enum ContentStyle { NONE (0), @@ -48,6 +55,24 @@ public class TmaMediaItem { } } + public enum TmaCustomAction { + HEART_PLUS_PLUS(CUSTOM_ACTION_PREFIX + "heart_plus_plus", R.string.heart_plus_plus, + R.drawable.ic_heart_plus_plus), + HEART_LESS_LESS(CUSTOM_ACTION_PREFIX + "heart_less_less", R.string.heart_less_less, + R.drawable.ic_heart_less_less); + + final String mId; + final int mNameId; + final int mIcon; + + TmaCustomAction(String id, int name, int icon) { + mId = id; + mNameId = name; + mIcon = icon; + } + + } + private final @MediaItem.Flags int mFlags; private final MediaMetadataCompat mMediaMetadata; private final ContentStyle mPlayableStyle; @@ -55,41 +80,87 @@ public class TmaMediaItem { /** Read only list. */ final List<TmaMediaItem> mChildren; + /** Read only list. */ + private final List<TmaMediaItem> mPlayableChildren; + /** Read only list. */ + final List<TmaCustomAction> mCustomActions; /** Read only list. Events triggered when starting the playback. */ final List<TmaMediaEvent> mMediaEvents; /** References another json file where to get extra children from. */ final String mInclude; + private @Nullable TmaMediaItem mParent; + int mHearts; + - public TmaMediaItem(@MediaItem.Flags int flags, ContentStyle playableStyle, - ContentStyle browsableStyle, MediaMetadataCompat metadata, - List<TmaMediaEvent> mediaEvents, List<TmaMediaItem> children, String include) { + public TmaMediaItem(@Flags int flags, ContentStyle playableStyle, ContentStyle browsableStyle, + MediaMetadataCompat metadata, List<TmaCustomAction> customActions, + List<TmaMediaEvent> mediaEvents, + List<TmaMediaItem> children, String include) { mFlags = flags; mPlayableStyle = playableStyle; mBrowsableStyle = browsableStyle; mMediaMetadata = metadata; + mCustomActions = Collections.unmodifiableList(customActions); mChildren = Collections.unmodifiableList(children); mMediaEvents = Collections.unmodifiableList(mediaEvents); mInclude = include; + List<TmaMediaItem> playableChildren = new ArrayList<>(children.size()); + for (TmaMediaItem child: mChildren) { + child.setParent(this); + if ((child.mFlags & FLAG_PLAYABLE) != 0) { + playableChildren.add(child); + } + } + mPlayableChildren = Collections.unmodifiableList(playableChildren); + } + + private void setParent(@Nullable TmaMediaItem parent) { + mParent = parent; } - public String getMediaId() { + @Nullable + TmaMediaItem getParent() { + return mParent; + } + + TmaMediaItem getPlayableByIndex(long index) { + return mPlayableChildren.get((int)index); + } + + @Nullable + TmaMediaItem getPrevious() { + if (mParent == null) return null; + List<TmaMediaItem> queueItems = mParent.mPlayableChildren; + int myIndex = queueItems.indexOf(this); + return (myIndex > 0) ? queueItems.get(myIndex - 1) : null; + } + + @Nullable + TmaMediaItem getNext() { + if (mParent == null) return null; + List<TmaMediaItem> queueItems = mParent.mPlayableChildren; + int myIndex = queueItems.indexOf(this); + return (myIndex < queueItems.size() - 1) ? queueItems.get(myIndex + 1) : null; + } + + String getMediaId() { return mMediaMetadata.getString(METADATA_KEY_MEDIA_ID); } /** Returns -1 if the duration key is unspecified or <= 0. */ - public long getDuration() { + long getDuration() { long result = mMediaMetadata.getLong(METADATA_KEY_DURATION); if (result <= 0) return -1; return result; } - public TmaMediaItem append(List<TmaMediaItem> children) { + TmaMediaItem append(List<TmaMediaItem> children) { List<TmaMediaItem> allChildren = new ArrayList<>(mChildren.size() + children.size()); allChildren.addAll(mChildren); allChildren.addAll(children); return new TmaMediaItem(mFlags, mPlayableStyle, mBrowsableStyle, mMediaMetadata, - mMediaEvents, allChildren, null); + mCustomActions, mMediaEvents, allChildren, null); } void updateSessionMetadata(MediaSessionCompat session) { @@ -100,6 +171,25 @@ public class TmaMediaItem { return new MediaItem(buildDescription(), mFlags); } + List<QueueItem> buildQueue() { + int count = mPlayableChildren.size(); + List<QueueItem> queue = new ArrayList<>(count); + for (int i = 0 ; i < count; i++) { + TmaMediaItem child = mPlayableChildren.get(i); + queue.add(new QueueItem(child.buildDescription(), i)); + } + return queue; + } + + /** Returns the id of the item in the queue. */ + long getQueueId() { + if (mParent != null) { + int index = mParent.mPlayableChildren.indexOf(this); + if (index >= 0) return index; + } + return MediaSessionCompat.QueueItem.UNKNOWN_ID; + } + private MediaDescriptionCompat buildDescription() { // Use the default media description but add our extras. diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java index f20bc99..c6213b2 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java @@ -18,6 +18,8 @@ package com.android.car.media.testmediaapp; import static android.media.AudioManager.AUDIOFOCUS_GAIN; import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SEEK_TO; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; @@ -29,7 +31,7 @@ import static android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR; import static com.android.car.media.common.MediaConstants.ERROR_RESOLUTION_ACTION_INTENT; import static com.android.car.media.common.MediaConstants.ERROR_RESOLUTION_ACTION_LABEL; -import android.annotation.Nullable; +import androidx.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -40,9 +42,11 @@ import android.os.Handler; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; +import android.widget.Toast; import com.android.car.media.testmediaapp.TmaMediaEvent.EventState; import com.android.car.media.testmediaapp.TmaMediaEvent.ResolutionIntent; +import com.android.car.media.testmediaapp.TmaMediaItem.TmaCustomAction; import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType; import com.android.car.media.testmediaapp.prefs.TmaPrefs; import com.android.car.media.testmediaapp.prefs.TmaPrefsActivity; @@ -55,20 +59,6 @@ public class TmaPlayer extends MediaSessionCompat.Callback { private static final String TAG = "TmaPlayer"; - // TODO: refactor actions (make some per item, per state ??)... - private static final long PLAYING_ACTIONS = PlaybackStateCompat.ACTION_PAUSE - | ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS - | ACTION_SKIP_TO_QUEUE_ITEM | ACTION_SEEK_TO; - - private static final long PAUSED_ACTIONS = PlaybackStateCompat.ACTION_PLAY - | ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS - | ACTION_SEEK_TO; - - private static final long STOPPED_ACTIONS = PlaybackStateCompat.ACTION_PLAY - | ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS - | ACTION_SEEK_TO; - - private final Context mContext; private final TmaPrefs mPrefs; private final TmaLibrary mLibrary; @@ -109,7 +99,7 @@ public class TmaPlayer extends MediaSessionCompat.Callback { PlaybackStateCompat.Builder state = new PlaybackStateCompat.Builder() .setState(event.mState.mValue, mCurrentPositionMs, mPlaybackSpeed) .setErrorMessage(event.mErrorCode.mValue, event.mErrorMessage) - .setActions(PLAYING_ACTIONS); + .setActions(addActions(ACTION_PAUSE)); if (ResolutionIntent.PREFS.equals(event.mResolutionIntent)) { Intent prefsIntent = new Intent(); prefsIntent.setClass(mContext, TmaPrefsActivity.class); @@ -121,24 +111,67 @@ public class TmaPlayer extends MediaSessionCompat.Callback { extras.putParcelable(ERROR_RESOLUTION_ACTION_INTENT, pendingIntent); state.setExtras(extras); } + + setActiveItemState(state); mSession.setPlaybackState(state.build()); } + /** Sets custom action, queue id, etc. */ + private void setActiveItemState(PlaybackStateCompat.Builder state) { + if (mActiveItem != null) { + for (TmaCustomAction action : mActiveItem.mCustomActions) { + String name = mContext.getResources().getString(action.mNameId); + state.addCustomAction(action.mId, name, action.mIcon); + } + state.setActiveQueueItemId(mActiveItem.getQueueId()); + } + } + + private void playItem(@Nullable TmaMediaItem item) { + if (item != null && item.getParent() != null) { + if (mIsPlaying) { + stopPlayback(); + } + mActiveItem = item; + mSession.setQueue(item.getParent().buildQueue()); + startPlayBack(true); + } + } + @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { super.onPlayFromMediaId(mediaId, extras); - mActiveItem = mLibrary.getMediaItemById(mediaId); - if (requestAudioFocus()) { - startPlayBack(); + playItem(mLibrary.getMediaItemById(mediaId)); + } + + @Override + public void onSkipToQueueItem(long id) { + super.onSkipToQueueItem(id); + if (mActiveItem != null && mActiveItem.getParent() != null) { + playItem(mActiveItem.getParent().getPlayableByIndex(id)); + } + } + + @Override + public void onSkipToNext() { + super.onSkipToNext(); + if (mActiveItem != null) { + playItem(mActiveItem.getNext()); + } + } + + @Override + public void onSkipToPrevious() { + super.onSkipToPrevious(); + if (mActiveItem != null) { + playItem(mActiveItem.getPrevious()); } } @Override public void onPlay() { super.onPlay(); - if (requestAudioFocus()) { - startPlayBack(); - } + startPlayBack(true); } @Override @@ -149,9 +182,8 @@ public class TmaPlayer extends MediaSessionCompat.Callback { mHandler.removeCallbacks(mTrackTimer); } mCurrentPositionMs = pos; - if (wasPlaying) { - startPlayBack(); - } + boolean requestAudioFocus = !wasPlaying; + startPlayBack(requestAudioFocus); } @Override @@ -167,11 +199,32 @@ public class TmaPlayer extends MediaSessionCompat.Callback { sendStopPlaybackState(); } - private boolean requestAudioFocus() { + @Override + public void onCustomAction(String action, Bundle extras) { + super.onCustomAction(action, extras); + if (mActiveItem != null) { + if (TmaCustomAction.HEART_PLUS_PLUS.mId.equals(action)) { + mActiveItem.mHearts++; + toast("" + mActiveItem.mHearts); + } else if (TmaCustomAction.HEART_LESS_LESS.mId.equals(action)) { + mActiveItem.mHearts--; + toast("" + mActiveItem.mHearts); + } + } + } + + /** Note: this is for quick feedback implementation, media apps should avoid toasts... */ + private void toast(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); + } + + private boolean audioFocusGranted() { return mAudioManager.requestAudioFocus(mAudioFocusRequest) == AUDIOFOCUS_REQUEST_GRANTED; } private void onProcessMediaEvent() { + if (mActiveItem == null) return; + TmaMediaEvent event = mActiveItem.mMediaEvents.get(mNextEventIndex); if (event.premiumAccountRequired() && @@ -205,7 +258,9 @@ public class TmaPlayer extends MediaSessionCompat.Callback { } } - private void startPlayBack() { + private void startPlayBack(boolean requestAudioFocus) { + if (requestAudioFocus && !audioFocusGranted()) return; + if (mActiveItem == null || mActiveItem.mMediaEvents.size() <= 0) { PlaybackStateCompat state = new PlaybackStateCompat.Builder() .setState(STATE_ERROR, mCurrentPositionMs, mPlaybackSpeed) @@ -217,6 +272,7 @@ public class TmaPlayer extends MediaSessionCompat.Callback { mActiveItem.updateSessionMetadata(mSession); + mHandler.removeCallbacks(mEventTrigger); mNextEventIndex = 0; mHandler.postDelayed(mEventTrigger, mActiveItem.mMediaEvents.get(0).mPostDelayMs); } @@ -224,11 +280,11 @@ public class TmaPlayer extends MediaSessionCompat.Callback { private void pausePlayback() { mCurrentPositionMs += (System.currentTimeMillis() - mPlaybackStartTimeMs) / mPlaybackSpeed; mHandler.removeCallbacks(mTrackTimer); - PlaybackStateCompat state = new PlaybackStateCompat.Builder() + PlaybackStateCompat.Builder state = new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_PAUSED, mCurrentPositionMs, mPlaybackSpeed) - .setActions(PAUSED_ACTIONS) - .build(); - mSession.setPlaybackState(state); + .setActions(addActions(ACTION_PLAY)); + setActiveItemState(state); + mSession.setPlaybackState(state.build()); mIsPlaying = false; } @@ -240,10 +296,25 @@ public class TmaPlayer extends MediaSessionCompat.Callback { } private void sendStopPlaybackState() { - PlaybackStateCompat state = new PlaybackStateCompat.Builder() + PlaybackStateCompat.Builder state = new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_STOPPED, mCurrentPositionMs, mPlaybackSpeed) - .setActions(STOPPED_ACTIONS) - .build(); - mSession.setPlaybackState(state); + .setActions(addActions(ACTION_PLAY)); + setActiveItemState(state); + mSession.setPlaybackState(state.build()); + } + + private long addActions(long actions) { + actions |= ACTION_PLAY_FROM_MEDIA_ID | ACTION_SKIP_TO_QUEUE_ITEM | ACTION_SEEK_TO; + + if (mActiveItem != null) { + if (mActiveItem.getNext() != null) { + actions |= ACTION_SKIP_TO_NEXT; + } + if (mActiveItem.getPrevious() != null) { + actions |= ACTION_SKIP_TO_PREVIOUS; + } + } + + return actions; } } diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaLoaderUtils.java b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaLoaderUtils.java index 7cc4eb5..125c3a7 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaLoaderUtils.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaLoaderUtils.java @@ -30,7 +30,9 @@ import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; class TmaLoaderUtils { @@ -83,6 +85,23 @@ class TmaLoaderUtils { } } + /** Returns the array mapped to the name of the given key, or empty if missing. */ + static <T extends Enum, U extends Enum> List<U> getEnumArray(JSONObject json, T key, + Map<String, U> enumMap) { + try { + JSONArray array = json.has(key.name()) ? json.getJSONArray(key.name()) : null; + int count = (array != null) ? array.length() : 0; + List<U> result = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + result.add(enumMap.get(array.getString(i))); + } + return result; + } catch (JSONException e) { + Log.e(TAG, "JSONException getting array for: " + key + " e: " + e); + return new ArrayList<>(); + } + } + /** Returns the string mapped to the name of the given key, or null if missing. */ @Nullable static <T extends Enum> String getString(JSONObject json, T key) { diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java index 4ec47e2..b6d1a7a 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaEventReader.java @@ -75,7 +75,7 @@ class TmaMediaEventReader { } @Nullable - public TmaMediaEvent fromJson(@Nullable JSONObject json) { + TmaMediaEvent fromJson(@Nullable JSONObject json) { if (json == null) return null; return new TmaMediaEvent( getEnum(json, Keys.STATE, mEventStates, EventState.NONE), diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaItemReader.java b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaItemReader.java index 3416a9f..2d4b845 100644 --- a/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaItemReader.java +++ b/TestMediaApp/src/com/android/car/media/testmediaapp/loader/TmaMediaItemReader.java @@ -23,15 +23,15 @@ import static com.android.car.media.testmediaapp.TmaMediaEvent.INSTANT_PLAYBACK; import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.enumNamesToValues; import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.getArray; import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.getEnum; +import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.getEnumArray; import static com.android.car.media.testmediaapp.loader.TmaLoaderUtils.getString; -import android.support.v4.media.MediaMetadataCompat; -import android.text.TextUtils; import android.util.Log; import androidx.annotation.Nullable; import com.android.car.media.testmediaapp.TmaMediaEvent; +import com.android.car.media.testmediaapp.TmaMediaItem.TmaCustomAction; import com.android.car.media.testmediaapp.TmaMediaItem; import com.android.car.media.testmediaapp.TmaMediaItem.ContentStyle; @@ -57,6 +57,7 @@ class TmaMediaItemReader { METADATA, CHILDREN, INCLUDE, + CUSTOM_ACTIONS, EVENTS } @@ -73,18 +74,20 @@ class TmaMediaItemReader { private final TmaMediaEventReader mMediaEventReader; private final Map<String, Integer> mFlags = new HashMap<>(2); private final Map<String, ContentStyle> mContentStyles; + private final Map<String, TmaCustomAction> mCustomActions; private TmaMediaItemReader() { mMediaMetadataReader = TmaMediaMetadataReader.getInstance(); mMediaEventReader = TmaMediaEventReader.getInstance(); mContentStyles = enumNamesToValues(ContentStyle.values()); + mCustomActions = enumNamesToValues(TmaMediaItem.TmaCustomAction.values()); mFlags.put("browsable", FLAG_BROWSABLE); mFlags.put("playable", FLAG_PLAYABLE); } @Nullable - public TmaMediaItem fromJson(@Nullable JSONObject json) { + TmaMediaItem fromJson(@Nullable JSONObject json) { if (json == null) return null; try { // Media events @@ -107,11 +110,11 @@ class TmaMediaItemReader { } - return new TmaMediaItem( - TmaLoaderUtils.parseFlags(getString(json, Keys.FLAGS), mFlags), + return new TmaMediaItem(TmaLoaderUtils.parseFlags(getString(json, Keys.FLAGS), mFlags), getEnum(json, Keys.PLAYABLE_HINT, mContentStyles, ContentStyle.NONE), getEnum(json, Keys.BROWSABLE_HINT, mContentStyles, ContentStyle.NONE), mMediaMetadataReader.fromJson(json.getJSONObject(Keys.METADATA.name())), + getEnumArray(json, Keys.CUSTOM_ACTIONS, mCustomActions), mediaEvents, mediaItems, getString(json, Keys.INCLUDE)); } catch (JSONException e) { Log.e(TAG, "Json failure: " + e); |