Showcase app: Added media row actions to the music fragment

With the latest support for custom actions from leanback support, showcase
app now demos Playlist and Favorite action buttons for each media item
row. Upon favoriting a song, the media row is highlighted with a
different color. Also, clicking on song details will start playing that
song.

Change-Id: I59111563bebea3b0bf28e12d74f71421dc971f88
diff --git a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml b/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
index a7e158b..31af3e1 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
@@ -16,6 +16,7 @@
         android:allowBackup="true"
         android:icon="@mipmap/app_banner_sample_app"
         android:label="@string/app_name"
+        android:supportsRtl="true"
         android:largeHeap="true"
         android:theme="@style/Theme.Example.LeanbackLauncher">
         <activity
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
index bc757cd..7713c1a 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.support.v17.leanback.app.PlaybackControlGlue;
 import android.support.v17.leanback.app.PlaybackOverlayFragment;
+import android.support.v17.leanback.supportleanbackshowcase.R;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
@@ -66,7 +67,6 @@
     private PlaybackControlsRow mControlsRow;
     private Runnable mRunnable;
     private Handler mHandler = new Handler();
-    private boolean mPaused = false;
     private boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized
     private OnMediaFileFinishedPlayingListener mMediaFileFinishedPlayingListener;
     private Action mSelectedAction; // the action which is currently selected by the user
@@ -87,10 +87,6 @@
         mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
 
-        // Setup controls and notify UI about change.
-        setFadingEnabled(false);
-        onStateChanged();
-
         // Register selected listener such that we know what action the user currently has focused.
         fragment.setOnItemViewSelectedListener(this);
     }
@@ -100,8 +96,8 @@
      * not required to call this method before playing the first file. However you have to call it
      * before playing a second one.
      */
-    public void reset() {
-        mPaused = mInitialized = false;
+    void reset() {
+        mInitialized = false;
         mPlayer.reset();
     }
 
@@ -137,8 +133,8 @@
      */
     public void setupControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
         // TODO: hahnr@ move into resources
-        presenter.setProgressColor(Color.parseColor("#feab91"));
-        presenter.setBackgroundColor(Color.parseColor("#db2a0f"));
+        presenter.setProgressColor(getContext().getColor(R.color.player_progress_color));
+        presenter.setBackgroundColor(getContext().getColor(R.color.player_background_color));
     }
 
     @Override public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
@@ -260,47 +256,12 @@
     }
 
     @Override protected void startPlayback(int speed) throws IllegalStateException {
-        if (mPaused) {
-            mPlayer.start();
-        } else if (!isMediaPlaying()) {
-            reset();
-            try {
-                if (mMediaSourceUri != null) mPlayer.setDataSource(getContext(), mMediaSourceUri);
-                else mPlayer.setDataSource(mMediaSourcePath);
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-                @Override public void onPrepared(MediaPlayer mp) {
-                    mInitialized = true;
-                    mPaused = false;
-                    mPlayer.start();
-                    onMetadataChanged();
-                    onStateChanged();
-                    updateProgress();
-                }
-            });
-            mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                @Override public void onCompletion(MediaPlayer mp) {
-                    if (mInitialized && mMediaFileFinishedPlayingListener != null)
-                        mMediaFileFinishedPlayingListener.onMediaFileFinishedPlaying(mMetaData);
-                }
-            });
-            mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
-                @Override public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                    mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
-                }
-            });
-            mPlayer.prepareAsync();
-            onStateChanged();
-        }
+        mPlayer.start();
     }
 
     @Override protected void pausePlayback() {
         if (mPlayer.isPlaying()) {
             mPlayer.pause();
-            mPaused = true;
         }
     }
 
@@ -326,7 +287,40 @@
      * @see MediaPlayer#setDataSource(Context, Uri)
      */
     public void setMediaSource(Uri uri) {
+        if (mMediaSourceUri != null && mMediaSourceUri.equals(uri)) {
+            return;
+        }
         mMediaSourceUri = uri;
+        reset();
+        try {
+            if (mMediaSourceUri != null) mPlayer.setDataSource(getContext(), mMediaSourceUri);
+            else mPlayer.setDataSource(mMediaSourcePath);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override public void onPrepared(MediaPlayer mp) {
+                mInitialized = true;
+                mPlayer.start();
+                onMetadataChanged();
+                onStateChanged();
+                updateProgress();
+            }
+        });
+        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override public void onCompletion(MediaPlayer mp) {
+                if (mInitialized && mMediaFileFinishedPlayingListener != null)
+                    mMediaFileFinishedPlayingListener.onMediaFileFinishedPlaying(mMetaData);
+            }
+        });
+        mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
+            @Override public void onBufferingUpdate(MediaPlayer mp, int percent) {
+                mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
+            }
+        });
+        mPlayer.prepareAsync();
+        onStateChanged();
     }
 
     /**
@@ -417,7 +411,6 @@
         private String mArtist;
         private Drawable mCover;
 
-
         public String getTitle() {
             return mTitle;
         }
@@ -441,6 +434,7 @@
         public void setCover(Drawable cover) {
             this.mCover = cover;
         }
+
     }
 
 }
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java
index fdb8ec4..885ebd7 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java
@@ -14,25 +14,19 @@
 
 package android.support.v17.leanback.supportleanbackshowcase.app.media;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.v17.leanback.app.PlaybackOverlayFragment;
 import android.support.v17.leanback.supportleanbackshowcase.utils.Constants;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.MediaPlayerGlue;
 import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.TrackListHeader;
 import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
 import android.support.v17.leanback.supportleanbackshowcase.models.Song;
 import android.support.v17.leanback.supportleanbackshowcase.models.SongList;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.*;
+import android.support.v17.leanback.widget.AbstractMediaItemPresenter;
 import android.util.Log;
 
 import com.google.gson.Gson;
@@ -43,10 +37,12 @@
  * This example shows how to play music files and build a simple track list.
  */
 public class MusicConsumptionExampleFragment extends PlaybackOverlayFragment implements
-        OnItemViewClickedListener, Song.OnSongRowClickListener,
+        BaseOnItemViewClickedListener, BaseOnItemViewSelectedListener,
         MediaPlayerGlue.OnMediaFileFinishedPlayingListener {
 
     private static final String TAG = "MusicConsumptionExampleFragment";
+    private static final int PLAYLIST_ACTION_ID = 0;
+    private static final int FAVORITE_ACTION_ID = 1;
     private ArrayObjectAdapter mRowsAdapter;
     private MediaPlayerGlue mGlue;
     private int mCurrentSongIndex = 0;
@@ -69,7 +65,38 @@
 
         String json = Utils.inputStreamToString(
                 getResources().openRawResource(R.raw.music_consumption_example));
+
+
         mSongList = new Gson().fromJson(json, SongList.class).getSongs();
+
+        Resources res = getActivity().getResources();
+
+        // For each song add a playlist and favorite actions.
+        for(Song song : mSongList) {
+            MultiActionsProvider.MultiAction[] mediaRowActions = new
+                    MultiActionsProvider.MultiAction[2];
+            MultiActionsProvider.MultiAction playlistAction = new
+                    MultiActionsProvider.MultiAction(PLAYLIST_ACTION_ID);
+            Drawable[] playlistActionDrawables = new Drawable[] {
+                    res.getDrawable(R.drawable.ic_playlist_add_white_24dp,
+                            getActivity().getTheme()),
+                    res.getDrawable(R.drawable.ic_playlist_add_filled_24dp,
+                            getActivity().getTheme())};
+            playlistAction.setDrawables(playlistActionDrawables);
+            mediaRowActions[0] = playlistAction;
+
+            MultiActionsProvider.MultiAction favoriteAction = new
+                    MultiActionsProvider.MultiAction(FAVORITE_ACTION_ID);
+            Drawable[] favoriteActionDrawables = new Drawable[] {
+                    res.getDrawable(R.drawable.ic_favorite_border_white_24dp,
+                            getActivity().getTheme()),
+                    res.getDrawable(R.drawable.ic_favorite_filled_24dp,
+                            getActivity().getTheme())};
+            favoriteAction.setDrawables(favoriteActionDrawables);
+            mediaRowActions[1] = favoriteAction;
+            song.setMediaRowActions(mediaRowActions);
+        }
+
         Song song = mSongList.get(mCurrentSongIndex);
         MediaPlayerGlue.MetaData metaData = new MediaPlayerGlue.MetaData();
         metaData.setArtist(song.getDescription());
@@ -79,7 +106,6 @@
         mGlue.setMetaData(metaData);
         mGlue.setMediaSource(uri);
 
-        setBackgroundType(PlaybackOverlayFragment.BG_LIGHT);
         addPlaybackControlsRow();
     }
 
@@ -94,48 +120,175 @@
         mGlue.reset();
     }
 
+    static class SongPresenter extends AbstractMediaItemPresenter {
+
+        SongPresenter() {
+            super();
+        }
+
+        SongPresenter(Context context, int themeResId) {
+            super(themeResId);
+            setHasMediaRowSeparator(true);
+        }
+
+        @Override
+        protected void onBindMediaDetails(ViewHolder vh, Object item) {
+
+            int favoriteTextColor =  vh.view.getContext().getResources().getColor(
+                    R.color.song_row_favorite_color);
+            Song song = (Song) item;
+            vh.getMediaItemNumberView().setText("" + song.getNumber());
+
+            String songTitle = song.getTitle() + " / " + song.getDescription();
+            vh.getMediaItemNameView().setText(songTitle);
+
+            vh.getMediaItemDurationView().setText("" + song.getDuration());
+
+            if (song.isFavorite()) {
+                vh.getMediaItemNumberView().setTextColor(favoriteTextColor);
+                vh.getMediaItemNameView().setTextColor(favoriteTextColor);
+                vh.getMediaItemDurationView().setTextColor(favoriteTextColor);
+            } else {
+                vh.getMediaItemNumberView().setTextAppearance(
+                        R.style.TextAppearance_Leanback_PlaybackMediaItemNumber);
+                vh.getMediaItemNameView().setTextAppearance(
+                        R.style.TextAppearance_Leanback_PlaybackMediaItemName);
+                vh.getMediaItemDurationView().setTextAppearance(
+                        R.style.TextAppearance_Leanback_PlaybackMediaItemDuration);
+            }
+        }
+    };
+
+    static class SongPresenterSelector extends PresenterSelector {
+        Presenter mRegularPresenter;
+        Presenter mFavoritePresenter;
+
+        /**
+         * Adds a presenter to be used for the given class.
+         */
+        public SongPresenterSelector setSongPresenterRegular(Presenter presenter) {
+            mRegularPresenter = presenter;
+            return this;
+        }
+
+        /**
+         * Adds a presenter to be used for the given class.
+         */
+        public SongPresenterSelector setSongPresenterFavorite(Presenter presenter) {
+            mFavoritePresenter = presenter;
+            return this;
+        }
+
+        @Override
+        public Presenter[] getPresenters() {
+            return new Presenter[]{mRegularPresenter, mFavoritePresenter};
+        }
+
+        @Override
+        public Presenter getPresenter(Object item) {
+            return ( (Song) item).isFavorite() ? mFavoritePresenter : mRegularPresenter;
+        }
+
+    }
+
+    static class TrackListHeaderPresenter extends AbstractMediaListHeaderPresenter {
+
+        TrackListHeaderPresenter() {
+            super();
+        }
+
+        @Override
+        protected void onBindMediaListHeaderViewHolder(ViewHolder vh, Object item) {
+            vh.getHeaderView().setText("Tracklist");
+        }
+    };
+
     private void addPlaybackControlsRow() {
-        final PlaybackControlsRowPresenter controlsPresenter = mGlue
-                .createControlsRowAndPresenter();
-        ClassPresenterSelector selector = new ClassPresenterSelector();
-        Song.Presenter songPresenter = new Song.Presenter(getActivity());
-        songPresenter.setOnClickListener(this);
-        selector.addClassPresenter(Song.class, songPresenter);
-        selector.addClassPresenter(TrackListHeader.class,
-                                   new TrackListHeader.Presenter(getActivity()));
-        selector.addClassPresenter(PlaybackControlsRow.class, controlsPresenter);
-        mRowsAdapter = new ArrayObjectAdapter(selector);
+        mRowsAdapter = new ArrayObjectAdapter(new ClassPresenterSelector()
+                .addClassPresenterSelector(Song.class, new SongPresenterSelector()
+                        .setSongPresenterRegular(new SongPresenter(getActivity(),
+                                R.style.Theme_Example_LeanbackMusic_RegularSongNumbers))
+                        .setSongPresenterFavorite(new SongPresenter(getActivity(),
+                                R.style.Theme_Example_LeanbackMusic_FavoriteSongNumbers)))
+                .addClassPresenter(TrackListHeader.class, new TrackListHeaderPresenter())
+                .addClassPresenter(PlaybackControlsRow.class,
+                        mGlue.createControlsRowAndPresenter()));
         mRowsAdapter.add(mGlue.getControlsRow());
         mRowsAdapter.add(new TrackListHeader());
         mRowsAdapter.addAll(2, mSongList);
         setAdapter(mRowsAdapter);
         setOnItemViewClickedListener(this);
+        setOnItemViewSelectedListener(this);
     }
 
+    public MusicConsumptionExampleFragment() {
+        super();
+    }
+
+
+
     @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                        RowPresenter.ViewHolder rowViewHolder, Row row) {
-        if (!(item instanceof Action)) return;
-        mGlue.onActionClicked((Action) item);
+                                        RowPresenter.ViewHolder rowViewHolder, Object row) {
+
+        if (item instanceof  Action) {
+            // if the clicked item is a primary or secondary action in the playback controller
+            mGlue.onActionClicked((Action) item);
+        } else if (row instanceof  Song) {
+            // if a media item row is clicked
+            Song clickedSong = (Song) row;
+            AbstractMediaItemPresenter.ViewHolder songRowVh =
+                    (AbstractMediaItemPresenter.ViewHolder) rowViewHolder;
+
+            // if an action within a media item row is clicked
+            if (item instanceof MultiActionsProvider.MultiAction) {
+                if ( ((MultiActionsProvider.MultiAction) item).getId() == FAVORITE_ACTION_ID) {
+                    MultiActionsProvider.MultiAction favoriteAction =
+                            (MultiActionsProvider.MultiAction) item;
+                    MultiActionsProvider.MultiAction playlistAction =
+                            songRowVh.getMediaItemRowActions()[0];
+                    favoriteAction.incrementIndex();
+                    playlistAction.incrementIndex();;
+
+                    clickedSong.setFavorite(!clickedSong.isFavorite());
+                    songRowVh.notifyDetailsChanged();
+                    songRowVh.notifyActionChanged(playlistAction);
+                    songRowVh.notifyActionChanged(favoriteAction);
+                }
+            } else if (item == null){
+                // if a media item details is clicked, start playing that media item
+                onSongDetailsClicked(clickedSong);
+            }
+
+        }
+
+
+    }
+
+    @Override
+    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                               RowPresenter.ViewHolder rowViewHolder, Object row) {
     }
 
 
-    @Override public void onSongRowClicked(Song song) {
-        mCurrentSongIndex = mSongList.indexOf(song);
+    public void onSongDetailsClicked(Song song) {
+        int nextSongIndex = mSongList.indexOf(song);
+        mCurrentSongIndex = nextSongIndex;
         startPlayback();
     }
 
 
     @Override public void onMediaFileFinishedPlaying(MediaPlayerGlue.MetaData song) {
         if (mGlue.repeatOne()) {
-            startPlayback();
-            return;
-        }
-        if (mGlue.useShuffle()) {
+        } else if (mGlue.useShuffle()) {
             mCurrentSongIndex = (int) (Math.random() * mSongList.size());
-        } else mCurrentSongIndex++;
-        if (mCurrentSongIndex >= mSongList.size()) {
-            mCurrentSongIndex = 0;
-            if (!mGlue.repeatAll()) return;
+        } else {
+            mCurrentSongIndex++;
+            if (mCurrentSongIndex >= mSongList.size()) {
+                mCurrentSongIndex = 0;
+                if (!mGlue.repeatAll()) {
+                    return;
+                }
+            }
         }
         startPlayback();
     }
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java
index ff794a2..0206748 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java
@@ -15,36 +15,7 @@
 
 package android.support.v17.leanback.supportleanbackshowcase.app.media;
 
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
 import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
 
 public class TrackListHeader extends Row {
-
-    public static class Presenter extends RowPresenter {
-
-        private final Context mContext;
-
-        public Presenter(Context context) {
-            mContext = context;
-            setHeaderPresenter(null);
-        }
-
-        @Override public boolean isUsingDefaultSelectEffect() {
-            return false;
-        }
-
-        @Override protected ViewHolder createRowViewHolder(ViewGroup parent) {
-            View view = LayoutInflater.from(mContext).inflate(R.layout.row_track_list_header, parent, false);
-            view.setFocusable(false);
-            view.setFocusableInTouchMode(false);
-            return new ViewHolder(view);
-        }
-    }
-
-
 }
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java
index 89729be..1ba29ca 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java
@@ -16,17 +16,22 @@
 package android.support.v17.leanback.supportleanbackshowcase.models;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.support.v17.leanback.supportleanbackshowcase.R;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
+import android.support.v17.leanback.widget.MultiActionsProvider;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import com.google.gson.Gson;
 import com.google.gson.annotations.SerializedName;
 
-public class Song extends Row {
+public class Song implements MultiActionsProvider {
 
     @SerializedName("title") private String mTitle = "";
     @SerializedName("description") private String mDescription = "";
@@ -35,12 +40,27 @@
     @SerializedName("file") private String mFile = null;
     @SerializedName("duration") private String mDuration = null;
     @SerializedName("number") private int mNumber = 0;
+    @SerializedName("favorite") private boolean mFavorite = false;
 
+    private MultiAction[] mMediaRowActions;
+
+
+    public void setMediaRowActions(MultiAction[] mediaRowActions) {
+        mMediaRowActions = mediaRowActions;
+    }
+
+    public MultiAction[] getMediaRowActions() {
+        return mMediaRowActions;
+    }
 
     public String getDuration() {
         return mDuration;
     }
 
+    public void setDuration(String duration) {
+        mDuration = duration;
+    }
+
     public int getNumber() {
         return mNumber;
     }
@@ -53,10 +73,26 @@
         return mDescription;
     }
 
+    public void setDescription(String description) {
+        mDescription = description;
+    }
+
     public String getTitle() {
         return mTitle;
     }
 
+    public void setTitle(String title) {
+        mTitle = title;
+    }
+
+    public boolean isFavorite() {
+        return mFavorite;
+    }
+
+    public void setFavorite(boolean favorite) {
+        mFavorite = favorite;
+    }
+
     public int getFileResource(Context context) {
         return context.getResources()
                       .getIdentifier(mFile, "raw", context.getPackageName());
@@ -67,50 +103,9 @@
                       .getIdentifier(mImage, "drawable", context.getPackageName());
     }
 
-    public interface OnSongRowClickListener {
-
-        void onSongRowClicked(Song song);
-
+    @Override
+    public MultiAction[] getActions() {
+        return mMediaRowActions;
     }
 
-    public static class Presenter extends RowPresenter implements View.OnClickListener {
-
-        private final Context mContext;
-        private OnSongRowClickListener mClickListener;
-
-
-        public Presenter(Context context) {
-            mContext = context;
-            setHeaderPresenter(null);
-        }
-
-        public void setOnClickListener(OnSongRowClickListener listener) {
-            mClickListener = listener;
-        }
-
-        @Override protected ViewHolder createRowViewHolder(ViewGroup parent) {
-            View view = LayoutInflater.from(mContext).inflate(R.layout.row_song, parent, false);
-            view.findViewById(R.id.rowContainer).setOnClickListener(this);
-            return new ViewHolder(view);
-        }
-
-        @Override public boolean isUsingDefaultSelectEffect() {
-            return false;
-        }
-
-        @Override protected void onBindRowViewHolder(ViewHolder vh, Object item) {
-            super.onBindRowViewHolder(vh, item);
-            Song song = (Song) item;
-            ((TextView) vh.view.findViewById(R.id.trackNumber)).setText("" + song.getNumber());
-            ((TextView) vh.view.findViewById(R.id.trackDuration)).setText(song.getDuration());
-            String text = song.getTitle() + " / " + song.getDescription();
-            ((TextView) vh.view.findViewById(R.id.trackName)).setText(text);
-            vh.view.findViewById(R.id.rowContainer).setTag(item);
-        }
-
-        @Override public void onClick(View v) {
-            if (mClickListener == null) return;
-            mClickListener.onSongRowClicked((Song) v.getTag());
-        }
-    }
 }
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml
new file mode 100644
index 0000000..03e16ad
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml
new file mode 100644
index 0000000..bce1999
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"
+        android:fillColor="@color/song_row_favorite_color"/>
+</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml
new file mode 100644
index 0000000..ac5d3f3
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"
+        android:fillColor="@color/song_row_favorite_color"/>
+</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml
new file mode 100644
index 0000000..4147e81
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_song.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_song.xml
deleted file mode 100644
index b8d660d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_song.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:lb="http://schemas.android.com/apk/res-auto"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:clipChildren="false"
-              android:clipToPadding="false"
-              android:orientation="horizontal"
-              android:paddingEnd="@dimen/lb_playback_controls_margin_end"
-              android:paddingStart="@dimen/lb_playback_controls_margin_start"
-              lb:rowHeight="wrap_content">
-
-
-    <RelativeLayout
-        android:id="@+id/rowContainer"
-        android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:background="@drawable/song_row_background"
-        android:focusable="true"
-        android:focusableInTouchMode="true">
-
-        <TextView
-            android:id="@+id/trackNumber"
-            android:layout_width="56dp"
-            android:layout_height="match_parent"
-            android:layout_marginLeft="32dp"
-            android:fontFamily="sans-serif-regular"
-            android:gravity="center_vertical"
-            android:text="1"
-            android:textColor="#FFFFFF"
-            android:textSize="18sp"/>
-
-
-        <TextView
-            android:id="@+id/trackName"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:layout_alignParentTop="true"
-            android:layout_toEndOf="@+id/trackNumber"
-            android:layout_toLeftOf="@+id/trackDuration"
-            android:fontFamily="sans-serif-regular"
-            android:gravity="center_vertical"
-            android:singleLine="true"
-            android:text="A Song!!11"
-            android:textColor="#FFFFFF"
-            android:textSize="18sp"/>
-
-
-        <TextView
-            android:id="@+id/trackDuration"
-            android:layout_width="66dp"
-            android:layout_height="48dp"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentTop="true"
-            android:layout_marginRight="32dp"
-            android:fontFamily="sans-serif-regular"
-            android:gravity="center_vertical|right"
-            android:text="1:13:54"
-            android:textColor="#80FFFFFF"
-            android:textSize="18sp"/>
-    </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_track_list_header.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_track_list_header.xml
deleted file mode 100644
index df16028..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/row_track_list_header.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                xmlns:lb="http://schemas.android.com/apk/res-auto"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:clipChildren="false"
-                android:clipToPadding="false"
-                android:paddingEnd="@dimen/lb_playback_controls_margin_end"
-                android:paddingStart="@dimen/lb_playback_controls_margin_start"
-                lb:rowHeight="wrap_content">
-
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="48dp"
-            android:layout_alignParentStart="true"
-            android:layout_alignParentTop="true"
-            android:background="#263238"
-            android:fontFamily="sans-serif-regular"
-            android:gravity="center_vertical"
-            android:paddingLeft="32dp"
-            android:text="Tracks"
-            android:textColor="#80EEEEEE"
-            android:textSize="18sp"/>
-    </RelativeLayout>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml
index 4c0091d..10d0973 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml
@@ -31,6 +31,7 @@
     <color name="default_card_footer_background_color">#37474F</color>
     <color name="selected_card_footer_background_color">#F0F</color>
 
+    <color name="lb_default_brand_color">#FF455A64</color>
     <color name="card_primary_text">#EEEEEE</color>
     <color name="card_secondary_text">#99EEEEEE</color>
 
@@ -41,10 +42,15 @@
     <color name="detail_view_actionbar_background">#04549D</color>
     <color name="detail_view_background">#0374BF</color>
     <color name="detail_view_related_background">#022A4E</color>
+    <color name="song_row_favorite_color">#FF6E40</color>
 
     <color name="app_guidedstep_actions_background">#C03800</color>
     <color name="app_guidedstep_subactions_background">#C03800</color>
     <color name="app_guidedstep_dialog_actions_background">#263238</color>
     <color name="settings_card_background">#0277BD</color>
     <color name="settings_card_background_focussed">#01579B</color>
+
+    <color name="player_progress_color">#feab91</color>
+    <color name="player_background_color">#db2a0f</color>
+
 </resources>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml
index f145062..33981d6 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml
@@ -46,7 +46,7 @@
     <string name="wizard_example_watch_sd">Watch in standard definition on the web and supported devices</string>
     <string name="wizard_example_rental_period">Rental period: start within 30 days,\nfinish within 24 hours</string>
     <string name="wizard_example_input_card">Enter credit card number</string>
-    <string name="wizard_example_expiration_date">Exp. date</string>
+    <string name="wizard_example_expiration_date">Exp Date</string>
     <string name="wizard_example_payment_method">Payment Method</string>
     <string name="wizard_example_toast_payment_method_clicked">\'Payment Method\' clicked.</string>
     <string name="wizard_example_rent">Rent</string>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml
index ca5b19e..4f624d8 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml
@@ -44,19 +44,18 @@
     </style>
 
 
-    <style name="MovieCardSimpleStyle" parent="Widget.Leanback.ImageCardViewStyle">
+    <style name="MovieCardTitleOnlyStyle" parent="Widget.Leanback.ImageCardViewStyle">
         <item name="lbImageCardViewType">Title</item>
         <item name="cardBackground">@null</item>
     </style>
 
     <!-- Theme corresponding to the MovieCardSimpleStyle -->
     <style name="MovieCardSimpleTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/MovieCardSimpleStyle </item>
+        <item name="imageCardViewStyle"> @style/MovieCardTitleOnlyStyle </item>
         <item name="imageCardViewImageStyle">@style/MovieCardImageStyle</item>
     </style>
 
-    <style name="MovieCardCompleteStyle" parent="MovieCardSimpleStyle">
-
+    <style name="MovieCardCompleteStyle" parent="MovieCardTitleOnlyStyle">
         <item name="lbImageCardViewType">Title|Content|IconOnLeft</item>
     </style>
 
@@ -147,7 +146,7 @@
     </style>
 
     <style name="GameCardStyle" parent="DefaultCardStyle">
-        <item name="lbImageCardViewType">Title|Content|IconOnLeft</item>
+        <item name="lbImageCardViewType">Title|Content|IconOnRight</item>
     </style>
 
     <!-- Theme corresponding to the GameCardStyle -->
@@ -218,4 +217,55 @@
         <item name="imageCardViewInfoAreaStyle">@style/IconCardInfoAreaStyle</item>
     </style>
 
+
+    <style name="MediaListHeaderStyle" parent="Widget.Leanback.PlaybackMediaListHeaderStyle">
+        <item name="android:background">#282248</item>
+    </style>
+
+    <style name="SharedMediaItemRowStyle" parent="Widget.Leanback.PlaybackMediaItemRowStyle">
+        <item name="android:background">#282248</item>
+    </style>
+
+    <style name="RegularMediaItemTextStyle" parent="TextAppearance.Leanback.PlaybackMediaItemNumber">
+        <item name="android:textColor">#FF6255</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+
+    <style name="RegularMediaItemNumberStyle" parent="Widget.Leanback.PlaybackMediaItemNumberStyle">
+        <item name="android:visibility">visible</item>
+        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
+    </style>
+
+    <style name="RegularMediaItemNameStyle" parent="Widget.Leanback.PlaybackMediaItemNameStyle">
+        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
+    </style>
+
+    <style name="RegularMediaItemDurationStyle" parent="Widget.Leanback.PlaybackMediaItemDurationStyle">
+        <item name="android:visibility">visible</item>
+        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
+    </style>
+
+
+    <style name="FavoriteMediaItemTextStyle" parent="TextAppearance.Leanback.PlaybackMediaItemNumber">
+        <item name="android:textColor">#FF6E40</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+    </style>
+
+    <style name="FavoriteMediaItemNumberStyle" parent="Widget.Leanback.PlaybackMediaItemNumberStyle">
+        <item name="android:visibility">visible</item>
+        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
+    </style>
+
+    <style name="FavoriteMediaItemNameStyle" parent="Widget.Leanback.PlaybackMediaItemNameStyle">
+        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
+    </style>
+
+    <style name="FavoriteMediaItemDurationStyle" parent="Widget.Leanback.PlaybackMediaItemDurationStyle">
+        <item name="android:visibility">visible</item>
+        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
+    </style>
+
 </resources>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
index 88a43c3..4aa06f6 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
@@ -73,4 +73,22 @@
         <item name="android:windowBackground">@drawable/background_sax</item>
      </style>
 
+    <style name="Theme.Example.LeanbackMusic.RegularSongNumbers">
+        <!--<item name="playbackMediaItemRowStyle">@style/SharedMediaItemRowStyle</item>-->
+        <item name="playbackMediaItemNumberStyle">@style/RegularMediaItemNumberStyle</item>
+        <item name="playbackMediaItemNameStyle">@style/RegularMediaItemNameStyle</item>
+        <item name="playbackMediaItemDurationStyle">@style/RegularMediaItemDurationStyle</item>
+    </style>
+
+    <style name="Theme.Example.LeanbackMusic.FavoriteSongNumbers">
+        <!--<item name="playbackMediaItemRowStyle">@style/SharedMediaItemRowStyle</item>-->
+        <item name="playbackMediaItemNumberStyle">@style/FavoriteMediaItemNumberStyle</item>
+        <item name="playbackMediaItemNameStyle">@style/FavoriteMediaItemNameStyle</item>
+        <item name="playbackMediaItemDurationStyle">@style/FavoriteMediaItemDurationStyle</item>
+    </style>
+
+    <style name="Theme.Example.LeanbackMusic.TrackListHeader">
+        <item name="playbackMediaListHeaderStyle">@style/MediaListHeaderStyle</item>
+    </style>
+
 </resources>