Adding custom title view example in Showcase sample.

Also, fixed NLP issues in PageAndListRowFragment.

Change-Id: I34a3040548aa533cd6b0655e29bc3d8b37edbc5d
diff --git a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml b/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
index 1329cd6..a7e158b 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
@@ -34,7 +34,7 @@
         <activity
                 android:name=".app.page.PageAndListRowActivity"
                 android:exported="true"
-                android:theme="@style/Theme.Example.LeanbackBrowse"/>
+                android:theme="@style/Theme.Example.Leanback.CustomTitle"/>
         <activity
             android:name=".app.wizard.WizardExampleActivity"
             android:exported="true"
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java
new file mode 100644
index 0000000..ef4a316
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java
@@ -0,0 +1,74 @@
+package android.support.v17.leanback.supportleanbackshowcase.app.page;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.supportleanbackshowcase.R;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * Custom title view to be used in {@link android.support.v17.leanback.app.BrowseFragment}.
+ */
+public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.Provider {
+    private final TextView mTitleView;
+    private final ImageView mBadgeView;
+
+    private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
+        @Override
+        public View getSearchAffordanceView() {
+            return null;
+        }
+
+        @Override
+        public void setTitle(CharSequence titleText) {
+            CustomTitleView.this.setTitle(titleText);
+        }
+
+        @Override
+        public void setBadgeDrawable(Drawable drawable) {
+            CustomTitleView.this.setBadgeDrawable(drawable);
+        }
+    };
+
+    public CustomTitleView(Context context) {
+        this(context, null);
+    }
+
+    public CustomTitleView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        View root  = LayoutInflater.from(context).inflate(R.layout.custom_titleview, this);
+        mTitleView = (TextView) root.findViewById(R.id.title_tv);
+        mBadgeView = (ImageView)root.findViewById(R.id.title_badge_iv);
+    }
+
+    public void setTitle(CharSequence title) {
+        if (title != null) {
+            mTitleView.setText(title);
+            mTitleView.setVisibility(View.VISIBLE);
+            mBadgeView.setVisibility(View.GONE);
+        }
+    }
+
+
+    public void setBadgeDrawable(Drawable drawable) {
+        if (drawable != null) {
+            mTitleView.setVisibility(View.GONE);
+            mBadgeView.setImageDrawable(drawable);
+            mBadgeView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public TitleViewAdapter getTitleViewAdapter() {
+        return mTitleViewAdapter;
+    }
+}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java
new file mode 100644
index 0000000..fc659b4
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java
@@ -0,0 +1,205 @@
+package android.support.v17.leanback.supportleanbackshowcase.app.page;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.supportleanbackshowcase.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnChildLaidOutListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+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.VerticalGridPresenter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A fragment for rendering items in a vertical grids.
+ */
+public class GridFragment extends Fragment implements BrowseFragment.MainFragmentAdapterProvider {
+    private static final String TAG = "VerticalGridFragment";
+    private static boolean DEBUG = false;
+
+    private ObjectAdapter mAdapter;
+    private VerticalGridPresenter mGridPresenter;
+    private VerticalGridPresenter.ViewHolder mGridViewHolder;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private Object mSceneAfterEntranceTransition;
+    private int mSelectedPosition = -1;
+    private BrowseFragment.MainFragmentAdapter mMainFragmentAdapter =
+            new BrowseFragment.MainFragmentAdapter(this) {
+                @Override
+                public void setEntranceTransitionState(boolean state) {
+                    GridFragment.this.setEntranceTransitionState(state);
+                }
+            };
+    /**
+     * Sets the grid presenter.
+     */
+    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+        if (gridPresenter == null) {
+            throw new IllegalArgumentException("Grid presenter may not be null");
+        }
+        mGridPresenter = gridPresenter;
+        mGridPresenter.setOnItemViewSelectedListener(mViewSelectedListener);
+        if (mOnItemViewClickedListener != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the grid presenter.
+     */
+    public VerticalGridPresenter getGridPresenter() {
+        return mGridPresenter;
+    }
+
+    /**
+     * Sets the object adapter for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the object adapter.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    final private OnItemViewSelectedListener mViewSelectedListener =
+            new OnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                           RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    int position = mGridViewHolder.getGridView().getSelectedPosition();
+                    if (DEBUG) Log.v(TAG, "grid selected position " + position);
+                    gridOnItemSelected(position);
+                    if (mOnItemViewSelectedListener != null) {
+                        mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                                rowViewHolder, row);
+                    }
+                }
+            };
+
+    final private OnChildLaidOutListener mChildLaidOutListener =
+            new OnChildLaidOutListener() {
+                @Override
+                public void onChildLaidOut(ViewGroup parent, View view, int position, long id) {
+                    if (position == 0) {
+                        showOrHideTitle();
+                    }
+                }
+            };
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    private void gridOnItemSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSelectedPosition = position;
+            showOrHideTitle();
+        }
+    }
+
+    private void showOrHideTitle() {
+        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
+                == null) {
+            return;
+        }
+        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
+            mMainFragmentAdapter.getFragmentHost().showTitleView(true);
+        } else {
+            mMainFragmentAdapter.getFragmentHost().showTitleView(false);
+        }
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.grid_fragment, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
+        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+        gridDock.addView(mGridViewHolder.view);
+        mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
+
+        mSceneAfterEntranceTransition = TransitionHelper.createScene(gridDock, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionState(true);
+            }
+        });
+
+        getMainFragmentAdapter().getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+        updateAdapter();
+
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mGridViewHolder = null;
+    }
+
+    @Override
+    public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
+        return mMainFragmentAdapter;
+    }
+
+    /**
+     * Sets the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+        }
+    }
+
+    private void updateAdapter() {
+        if (mGridViewHolder != null) {
+            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+            if (mSelectedPosition != -1) {
+                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+
+    void setEntranceTransitionState(boolean afterTransition) {
+        mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
+    }
+}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java
index b4d9449..819caba 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java
@@ -7,11 +7,9 @@
 import android.support.v17.leanback.app.BackgroundManager;
 import android.support.v17.leanback.app.BrowseFragment;
 import android.support.v17.leanback.app.RowsFragment;
-import android.support.v17.leanback.app.VerticalGridFragment;
 import android.support.v17.leanback.supportleanbackshowcase.R;
 import android.support.v17.leanback.supportleanbackshowcase.app.details.ShadowRowPresenterSelector;
 import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.IconCardPresenter;
 import android.support.v17.leanback.supportleanbackshowcase.models.Card;
 import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
 import android.support.v17.leanback.supportleanbackshowcase.utils.CardListRow;
@@ -22,7 +20,6 @@
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ListRowPresenter;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.PageRow;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
@@ -55,9 +52,6 @@
 
     private ArrayObjectAdapter mRowsAdapter;
 
-    public PageAndListRowFragment() {
-    }
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -73,9 +67,7 @@
         setHeadersState(HEADERS_ENABLED);
         setHeadersTransitionOnBackEnabled(true);
         setBrandColor(getResources().getColor(R.color.fastlane_background));
-        setTitle(getString(R.string.page_list_row_title));
-        setBadgeDrawable(getActivity().getResources().getDrawable(
-                R.drawable.abc_ab_share_pack_mtrl_alpha));
+        setTitle("Title goes here");
         setOnSearchClickedListener(new View.OnClickListener() {
 
             @Override
@@ -132,7 +124,7 @@
             Row row = (Row)rowObj;
             mBackgroundManager.setDrawable(null);
             if (row.getHeaderItem().getId() == HEADER_ID_1) {
-                return new SampleFragmentA(mBackgroundManager);
+                return new SampleFragmentA();
             } else if (row.getHeaderItem().getId() == HEADER_ID_2) {
                 return new SampleFragmentB();
             } else if (row.getHeaderItem().getId() == HEADER_ID_3) {
@@ -155,26 +147,20 @@
     /**
      * Simple page fragment implementation.
      */
-    public static class SampleFragmentA extends VerticalGridFragment
-            implements MainFragmentAdapterProvider {
-
+    public static class SampleFragmentA extends GridFragment {
         private static final int COLUMNS = 4;
-        private final MainFragmentAdapter mMainFragmentAdapter =
-                new PageAndListRowFragment.PageFragmentAdapterImpl(this);
         private final int ZOOM_FACTOR = FocusHighlight.ZOOM_FACTOR_SMALL;
-        private final BackgroundManager mBackgroundManager;
         private ArrayObjectAdapter mAdapter;
 
-        public SampleFragmentA(BackgroundManager backgroundManager) {
-            this.mBackgroundManager  = backgroundManager;
-        }
-
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setupAdapter();
+            loadData();
+            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
         }
 
+
         private void setupAdapter() {
             VerticalGridPresenter presenter = new VerticalGridPresenter(ZOOM_FACTOR);
             presenter.setNumberOfColumns(COLUMNS);
@@ -184,41 +170,19 @@
             mAdapter = new ArrayObjectAdapter(cardPresenter);
             setAdapter(mAdapter);
 
-            setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            setOnItemViewClickedListener(new OnItemViewClickedListener() {
                 @Override
-                public void onItemSelected(
+                public void onItemClicked(
                         Presenter.ViewHolder itemViewHolder,
                         Object item,
                         RowPresenter.ViewHolder rowViewHolder,
                         Row row) {
-
                     Card card = (Card)item;
-                    if(card.getLocalImageResourceName() != null) {
-                        int resourceId = getActivity().getResources().getIdentifier(
-                                card.getLocalImageResourceName(),
-                                "drawable",
-                                getActivity().getPackageName());
-
-                        mBackgroundManager.setDrawable(getResources().getDrawable(resourceId));
-                    }
+                    Toast.makeText(getActivity(),
+                            "Clicked on "+card.getTitle(),
+                            Toast.LENGTH_SHORT).show();
                 }
             });
-
-            prepareEntranceTransition();
-
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    loadData();
-                    startEntranceTransition();
-                }
-            }, 200);
-        }
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-            getMainFragmentAdapter().getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
         }
 
         private void loadData() {
@@ -227,17 +191,6 @@
             CardRow cardRow = new Gson().fromJson(json, CardRow.class);
             mAdapter.addAll(0, cardRow.getCards());
         }
-
-        @Override
-        public View onCreateView(
-                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-
-        @Override
-        public MainFragmentAdapter getMainFragmentAdapter() {
-            return mMainFragmentAdapter;
-        }
     }
 
     /**
@@ -263,33 +216,28 @@
             });
         }
 
-
         @Override
-        public void onAttach(Activity activity) {
-            super.onAttach(activity);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    createRows();
-                }
-            }, 200);
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            createRows();
+            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
         }
 
         private void createRows() {
-            if (isAdded()) {
                 String json = Utils.inputStreamToString(getResources().openRawResource(
-                        R.raw.cards_example));
+                        R.raw.page_row_example));
                 CardRow[] rows = new Gson().fromJson(json, CardRow[].class);
                 for (CardRow row : rows) {
-                    mRowsAdapter.add(createCardRow(row));
+                    if (row.getType() == CardRow.TYPE_DEFAULT) {
+                        mRowsAdapter.add(createCardRow(row));
+                    }
                 }
-            }
         }
 
-        private ListRow createCardRow(CardRow cardRow) {
+        private Row createCardRow(CardRow cardRow) {
             PresenterSelector presenterSelector = new CardPresenterSelector(getActivity());
             ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
-            for(Card card : cardRow.getCards()) {
+            for (Card card : cardRow.getCards()) {
                 adapter.add(card);
             }
 
@@ -325,6 +273,8 @@
                         R.raw.icon_example));
                 CardRow cardRow = new Gson().fromJson(json, CardRow.class);
                 mRowsAdapter.add(createCardRow(cardRow));
+                getMainFragmentAdapter().getFragmentHost().notifyDataReady(
+                        getMainFragmentAdapter());
             }
         }
 
@@ -368,6 +318,7 @@
         public void onResume() {
             super.onResume();
             mWebview.loadUrl("https://www.google.com/policies/terms");
+            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
         }
     }
 }
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml
new file mode 100644
index 0000000..f3ac0df
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+    <!--<android.support.v17.leanback.widget.SearchOrbView-->
+        <!--android:id="@+id/search_orb"-->
+        <!--android:layout_height="wrap_content"-->
+        <!--android:layout_width="wrap_content"-->
+        <!--android:transitionGroup="true"-->
+        <!--android:layout_gravity="center_vertical|start"-->
+        <!--android:layout_marginStart="56dp" />-->
+
+    <AnalogClock
+            android:id="@+id/clock"
+            android:layout_width="80dp"
+            android:layout_height="80dp"
+            android:padding="6dp"
+            android:layout_alignParentEnd="true"
+            android:layout_centerVertical="true"
+            android:layout_marginEnd="24dp" />
+
+    <ImageView
+            android:id="@+id/title_badge_iv"
+            android:layout_width="wrap_content"
+            android:layout_height="224dp"
+            android:adjustViewBounds="true"
+            android:layout_gravity="center_vertical|end"
+            android:layout_toStartOf="@id/clock"
+            android:src="@null"
+            android:layout_centerVertical="true"
+            android:visibility="gone" />
+
+    <TextView
+            android:id="@+id/title_tv"
+            android:textAppearance="@android:style/TextAppearance.Large"
+            android:visibility="gone"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="24dp"
+            android:layout_centerVertical="true"
+            android:layout_toStartOf="@id/clock" />
+
+</merge>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml
new file mode 100644
index 0000000..4e67908
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/browse_dummy"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent" >
+
+    <android.support.v17.leanback.widget.BrowseFrameLayout
+            android:id="@+id/grid_frame"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            android:descendantFocusability="afterDescendants"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" >
+
+        <FrameLayout
+                android:id="@+id/browse_grid_dock"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+
+    </android.support.v17.leanback.widget.BrowseFrameLayout>
+</FrameLayout>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml
new file mode 100644
index 0000000..a5de787
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v17.leanback.supportleanbackshowcase.app.page.CustomTitleView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/browse_title_group"
+        android:padding="16dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json
new file mode 100644
index 0000000..5e686d7
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json
@@ -0,0 +1,515 @@
+[
+  {
+    "title": "One Line Title",
+    "cards": [
+      {
+        "type": "MOVIE",
+        "title": "Deep Into The Deep Sleep",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_01"
+      },
+      {
+        "type": "MOVIE",
+        "title": "We",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_02"
+      },
+      {
+        "type": "MOVIE",
+        "title": "The Fairy Story Of A Legend",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_03"
+      },
+      {
+        "type": "MOVIE",
+        "title": "Cursed",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_04"
+      },
+      {
+        "type": "MOVIE",
+        "title": "My Crazy One",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_05"
+      },
+      {
+        "type": "MOVIE",
+        "title": "Gone",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_06"
+      },
+      {
+        "type": "MOVIE",
+        "title": "A Cold Night To Stay",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_07"
+      },
+      {
+        "type": "MOVIE",
+        "title": "The Silence",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_08"
+      },
+      {
+        "type": "MOVIE",
+        "title": "Hear The Roar",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_09"
+      }
+    ]
+  },
+  {
+    "title": "Two Line Title",
+    "cards": [
+      {
+        "type": "MOVIE_BASE",
+        "title": "Deep Into The Deep Sleep",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_01"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "We",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_02"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "The Fairy Story Of A Legend",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_03"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "Cursed",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_04"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "My Crazy One",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_05"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "Gone",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_06"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "A Cold Night To Stay",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_07"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "The Silence",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_08"
+      },
+      {
+        "type": "MOVIE_BASE",
+        "title": "Hear The Roar",
+        "description": "$3.99",
+        "localImageResource": "card_image_movie_09"
+      }
+    ]
+  },
+  {
+    "title": "Two Line Title + Icon",
+    "cards": [
+      {
+        "type": "MOVIE_COMPLETE",
+        "description": "$3.99",
+        "title": "Deep Into The Deep Sleep",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_01"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "We",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_02"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "The Fairy",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_03"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "Cursed",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_04"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "My Crazy One",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_05"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "Gone",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_06"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "A Cold Night To Stay",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_07"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "description": "$3.99",
+        "title": "The Silence",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_08"
+      },
+      {
+        "type": "MOVIE_COMPLETE",
+        "title": "Hear The Roar",
+        "description": "$3.99",
+        "footerIconLocalImageResource": "stars_red",
+        "localImageResource": "card_image_movie_09"
+      }
+    ]
+  },
+  {
+    "title": "Title + Description",
+    "cards": [
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#bf360C",
+        "localImageResource": "card_image_music_02"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#b93221",
+        "localImageResource": "card_image_music_03"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#311b92",
+        "localImageResource": "card_image_music_04"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#33691e",
+        "localImageResource": "card_image_music_05"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#37474f",
+        "localImageResource": "card_image_music_06"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#3e2723",
+        "localImageResource": "card_image_music_08"
+      },
+      {
+        "type": "SQUARE_BIG",
+        "title": "Blue in Green",
+        "description": "Miles Davis",
+        "footerColor": "#01579B",
+        "localImageResource": "card_image_music_09"
+      }
+    ]
+  },
+  {
+    "title": "Title + Description + Icon",
+    "cards": [
+      {
+        "type": "GAME",
+        "title": "Crazy One",
+        "description": "Purchased",
+        "localImageResource": "game_crazy_one",
+        "footerIconLocalImageResource": "ic_installed"
+      },
+      {
+        "type": "GAME",
+        "title": "Cursed",
+        "description": "Purchased",
+        "localImageResource": "game_cursed",
+        "footerIconLocalImageResource": "ic_installed"
+      },
+      {
+        "type": "GAME",
+        "title": "fairy",
+        "description": "Purchased",
+        "localImageResource": "game_fairy",
+        "footerIconLocalImageResource": "ic_installed"
+      },
+      {
+        "type": "GAME",
+        "title": "Hear The Roar",
+        "description": "Purchased",
+        "localImageResource": "game_hear_the_roar",
+        "footerIconLocalImageResource": "ic_installed"
+      },
+      {
+        "type": "GAME",
+        "title": "Silence",
+        "description": "Purchased",
+        "localImageResource": "game_silence",
+        "footerIconLocalImageResource": "ic_installed"
+      }
+    ]
+  },
+  {
+    "title": "Title + Description (Wide)",
+    "cards": [
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_01"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_02"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_03"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_04"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_05"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_06"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_07"
+      },
+      {
+        "type": "DEFAULT",
+        "title": "Marseille sea food tour",
+        "description": "9,089 views   3 years ago   by ADELAIDE",
+        "localImageResource": "coffee_and_tea_08"
+      }
+    ]
+  },
+  {
+    "title": "BaseCardView Info On The Right",
+    "cards": [
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_02"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_03"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_04"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_05"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_06"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_07"
+      },
+      {
+        "type": "SIDE_INFO",
+        "title": "The Life Aquatic",
+        "description": "Seu Jorge",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "card_image_music_08"
+      }
+    ]
+  },
+  {
+    "title": "BaseCardView using TextView",
+    "cards": [
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_01"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_02"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_03"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_04"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_05"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_06"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_07"
+      },
+      {
+        "type": "TEXT",
+        "title": "Jonathan Max",
+        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
+        "localImageResource": "face_08"
+      }
+    ]
+  },
+  {
+    "title": "ImageCardView Customize Style",
+    "cards": [
+      {
+        "type": "SINGLE_LINE",
+        "title": "Action & Adventure",
+        "footerColor": "#dd004e",
+        "localImageResource": "category_action"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Animation",
+        "footerColor": "#c51162",
+        "localImageResource": "category_animation"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Classics",
+        "footerColor": "#9c27b0",
+        "localImageResource": "category_classics"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Comedy",
+        "footerColor": "#cf4900",
+        "localImageResource": "category_comedy"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Crime",
+        "footerColor": "#3f51b5",
+        "localImageResource": "category_crime"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Documentary",
+        "footerColor": "#02639b",
+        "localImageResource": "category_documentary"
+      },
+      {
+        "type": "SINGLE_LINE",
+        "title": "Drama",
+        "footerColor": "#2a56c6",
+        "localImageResource": "category_drama"
+      }
+    ]
+  },
+  {
+    "title": "ImageCardView with onFocusChange listener",
+    "shadow": false,
+    "cards": [
+      {
+        "type": "ICON",
+        "title": "Settings",
+        "localImageResource": "ic_settings_settings"
+      },
+      {
+        "type": "ICON",
+        "title": "WiFi",
+        "localImageResource": "ic_settings_wifi_3_bar"
+      },
+      {
+        "type": "ICON",
+        "title": "Parental Control",
+        "localImageResource": "ic_settings_parental_control"
+      },
+      {
+        "type": "ICON",
+        "title": "Time",
+        "localImageResource": "ic_settings_time"
+      }
+    ]
+  }
+]
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
index 9edbe2b..88a43c3 100644
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
+++ b/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
@@ -34,6 +34,11 @@
         <item name="defaultSearchBrightColor">@color/search_bright_color</item>
     </style>
 
+    <style name="Theme.Example.Leanback.CustomTitle" parent="Theme.Example.LeanbackBrowse">
+        <item name="browseTitleViewLayout">@layout/titleview</item>
+        <item name="browseRowsMarginTop">120dp</item>
+    </style>
+
     <style name="Theme.Example.LeanbackVerticalGrid" parent="Theme.Leanback.VerticalGrid">
         <item name="android:windowBackground">@drawable/background_food</item>
     </style>