Expanding CarDrawerActivity for nested navigation

- CarDrawerActivity now works with CarDrawerAdapter and subclasses to
  display Drawer content. It includes a CarDrawerController that handles
  nested navigation (it maintains a stack of CarDrawerAdapter's). Also
  added progress-bar that can be displayed while loading the content for
  next level of drawer items.
- Added CarDrawerAdapter and sublcasses CarDrawerListAdapter and
  CarDrawerEmptyAdater for use by consumers of CarDrawerActivity.

Bug: 32019250
Test: Manually

Change-Id: I0e3ed0c2fc7734d596ee4ca21dfefc6e17e18897
diff --git a/car-stream-ui-lib/res/drawable-hdpi/ic_list_view_disable.png b/car-stream-ui-lib/res/drawable-hdpi/ic_list_view_disable.png
new file mode 100644
index 0000000..651ee62
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable-hdpi/ic_list_view_disable.png
Binary files differ
diff --git a/car-stream-ui-lib/res/drawable-mdpi/ic_list_view_disable.png b/car-stream-ui-lib/res/drawable-mdpi/ic_list_view_disable.png
new file mode 100644
index 0000000..8de7968
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable-mdpi/ic_list_view_disable.png
Binary files differ
diff --git a/car-stream-ui-lib/res/drawable-xhdpi/ic_list_view_disable.png b/car-stream-ui-lib/res/drawable-xhdpi/ic_list_view_disable.png
new file mode 100644
index 0000000..82adcb2
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable-xhdpi/ic_list_view_disable.png
Binary files differ
diff --git a/car-stream-ui-lib/res/drawable-xxhdpi/ic_list_view_disable.png b/car-stream-ui-lib/res/drawable-xxhdpi/ic_list_view_disable.png
new file mode 100644
index 0000000..fc64935
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable-xxhdpi/ic_list_view_disable.png
Binary files differ
diff --git a/car-stream-ui-lib/res/layout/car_drawer_activity.xml b/car-stream-ui-lib/res/layout/car_drawer_activity.xml
index 631240b..88524e0 100644
--- a/car-stream-ui-lib/res/layout/car_drawer_activity.xml
+++ b/car-stream-ui-lib/res/layout/car_drawer_activity.xml
@@ -35,7 +35,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="left"
-            android:layout_marginEnd="@dimen/car_drawer_margin_right"
+            android:layout_marginEnd="@dimen/car_drawer_right_margin"
             android:background="@color/car_card"
             android:paddingTop="@dimen/lens_header_height" >
 
@@ -43,6 +43,15 @@
                 android:id="@+id/drawer_list"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" />
+
+            <ProgressBar
+                android:id="@+id/drawer_progress"
+                android:layout_width="@dimen/car_drawer_progress_bar_size"
+                android:layout_height="@dimen/car_drawer_progress_bar_size"
+                android:layout_gravity="center"
+                android:indeterminate="true"
+                android:visibility="gone" />
+
         </FrameLayout>
     </android.support.v4.widget.DrawerLayout>
 
diff --git a/car-stream-ui-lib/res/layout/car_list_item_empty.xml b/car-stream-ui-lib/res/layout/car_list_item_empty.xml
new file mode 100644
index 0000000..0aaff22
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_empty.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginLeft="@dimen/car_drawer_disabled_list_margin_side"
+    android:focusable="false"
+    android:orientation="vertical"
+    android:background="@drawable/car_list_item_background" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_list_item_icon_size"
+        android:layout_height="@dimen/car_list_item_icon_size"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/car_drawer_disabled_list_icon_margin_top"
+        android:layout_marginBottom="@dimen/car_drawer_disabled_list_icon_margin_bottom" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/car_drawer_disabled_list_margin_side"
+        android:gravity="center"
+        style="@style/CarBody1" />
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_menu_list_item_normal.xml b/car-stream-ui-lib/res/layout/car_menu_list_item_normal.xml
new file mode 100644
index 0000000..1c14bdb
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_menu_list_item_normal.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<!-- NOTE: If you are changing anything here, you may need to update car_menu_list_item_small.xml.
+    DrawerItemViewHolder inflates one of these two layouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_list_item_height"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:background="@drawable/car_list_item_background" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_list_item_icon_size"
+        android:layout_height="@dimen/car_list_item_icon_size"
+        android:layout_marginRight="@dimen/car_list_item_icon_right_margin"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerCrop" />
+    <LinearLayout
+        android:id="@+id/text_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical" >
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/car_text_vertical_margin"
+            style="@style/CarBody1"
+            android:singleLine="true" />
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/CarBody2"
+            android:ellipsize="end"
+            android:singleLine="true" />
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/right_icon"
+        android:layout_width="@dimen/car_list_item_right_icon_size"
+        android:layout_height="@dimen/car_list_item_right_icon_size"
+        android:scaleType="fitCenter"
+        android:layout_marginEnd="@dimen/car_drawer_item_right_icon_margin_end"
+        android:layout_gravity="center_vertical"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/layout/car_menu_list_item_small.xml b/car-stream-ui-lib/res/layout/car_menu_list_item_small.xml
new file mode 100644
index 0000000..a1da2b2
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_menu_list_item_small.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<!-- NOTE: If you are changing anything here, you may need to update car_menu_list_item_normal.xml.
+    DrawerItemViewHolder inflates one of these two layouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_list_item_height_small"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:background="@drawable/car_list_item_background" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_list_item_small_icon_size"
+        android:layout_height="@dimen/car_list_item_small_icon_size"
+        android:layout_marginRight="@dimen/car_list_item_icon_right_margin"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerCrop" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="@dimen/car_text_vertical_margin"
+        style="@style/CarBody1"
+        android:singleLine="true" />
+    <ImageView
+        android:id="@+id/right_icon"
+        android:layout_width="@dimen/car_list_item_right_icon_size"
+        android:layout_height="@dimen/car_list_item_right_icon_size"
+        android:scaleType="fitCenter"
+        android:layout_marginEnd="@dimen/car_drawer_item_right_icon_margin_end"
+        android:layout_gravity="center_vertical"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/values/dimens.xml b/car-stream-ui-lib/res/values/dimens.xml
index 67d5fbd..f350db7 100644
--- a/car-stream-ui-lib/res/values/dimens.xml
+++ b/car-stream-ui-lib/res/values/dimens.xml
@@ -46,4 +46,18 @@
 
     <!-- The default size for the icons within the touch targets in car applications.. -->
     <dimen name="stream_button_icon_size">36dp</dimen>
+
+    <!-- Size of progress-bar in Drawer -->
+    <dimen name="car_drawer_progress_bar_size">48dp</dimen>
+
+    <!-- NOTE: car-support defines a similarly named value, but the value varies by screen-size. We
+        do not want that -->
+    <dimen name="car_drawer_right_margin">96dp</dimen>
+
+    <dimen name="car_drawer_item_right_icon_margin_end">32dp</dimen>
+
+    <!-- Used by car_list_item_empty.xml -->
+    <dimen name="car_drawer_disabled_list_margin_side">16dp</dimen>
+    <dimen name="car_drawer_disabled_list_icon_margin_top">48dp</dimen>
+    <dimen name="car_drawer_disabled_list_icon_margin_bottom">22dp</dimen>
 </resources>
diff --git a/car-stream-ui-lib/src/com/android/car/app/CarDrawerActivity.java b/car-stream-ui-lib/src/com/android/car/app/CarDrawerActivity.java
index 50de9b1..39e9838 100644
--- a/car-stream-ui-lib/src/com/android/car/app/CarDrawerActivity.java
+++ b/car-stream-ui-lib/src/com/android/car/app/CarDrawerActivity.java
@@ -22,27 +22,32 @@
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
 import android.view.Gravity;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.ProgressBar;
 
 import com.android.car.stream.ui.R;
 
+import java.util.Stack;
+
 /**
  * Common base Activity for car apps that need to present a Drawer.
  * <p>
  * This Activity manages the overall layout. To use it sub-classes need to:
  * <ul>
- *     <li>Provide the items for the Drawer by supplying a adapter to the PagedListView (see {@link
- *     #getDrawerListView()}). The Adapter should ideally be based on {@link DrawerItemViewHolder}
- *      since that produces items with the right layout and provides click-handling.</li>
+ *     <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.</li>
  *     <li>Add their main content to the container FrameLayout
  *     (with id = {@link #getContentContainerId()}_</li>
  * </ul>
  * This class will take care of drawer toggling and display.
  * <p>
+ * The rootAdapter can implement nested-navigation, in its click-handling, by passing the
+ * CarDrawerAdapter for the next level to {@link #switchToAdapter(CarDrawerAdapter)}. This
+ * activity will maintain a stack of such adapters. When the user navigates up, it will pop the top
+ * adapter off and display its contents again.
+ * <p>
  * Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
  * derivative.
  * <p>
@@ -52,8 +57,10 @@
 public abstract class CarDrawerActivity extends AppCompatActivity {
     private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
 
+    private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
     private DrawerLayout mDrawerLayout;
     private PagedListView mDrawerList;
+    private ProgressBar mProgressBar;
     private View mDrawerContent;
     private Toolbar mToolbar;
     private ActionBarDrawerToggle mDrawerToggle;
@@ -66,37 +73,57 @@
         mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
         mDrawerContent = findViewById(R.id.drawer_content);
         mDrawerList = (PagedListView)findViewById(R.id.drawer_list);
+        mProgressBar = (ProgressBar)findViewById(R.id.drawer_progress);
 
         mToolbar = (Toolbar) findViewById(R.id.main_toolbar);
         setSupportActionBar(mToolbar);
+
+        // Init drawer adapter stack.
+        CarDrawerAdapter rootAdapter = getRootAdapter();
+        mAdapterStack.push(rootAdapter);
+        mToolbar.setTitle(rootAdapter.getTitleResId());
+        mDrawerList.setAdapter(rootAdapter);
+
         setupDrawerToggling();
     }
 
     /**
-     * Get the DrawerLayout that this activity owns. Callers can perform operations like opening/
-     * closing or adding listeners.
-     *
-     * @return DrawerLayout managed by this activity.
+     * @return Adapter for root content of the Drawer.
      */
-    protected DrawerLayout getDrawerLayout() {
-        return mDrawerLayout;
-    }
+    protected abstract CarDrawerAdapter getRootAdapter();
 
     /**
-     * Get the PagedListView that will display the main content of the drawer. Sub-classes should
-     * supply content via {@link PagedListView#setAdapter(RecyclerView.Adapter)}.
+     * Used to pass in next level of items to display in the Drawer, including updated title. It is
+     * pushed on top of the existing adapter in a stack. Navigating up from this level later will
+     * pop this adapter off and surface contents of the next adapter at the top of the stack (and
+     * its title).
      *
-     * @return PagedListView used to display main drawer content.
+     * @param adapter Adapter for next level of content in the drawer.
      */
-    protected PagedListView getDrawerListView() {
-        return mDrawerList;
+    protected final void switchToAdapter(CarDrawerAdapter adapter) {
+        mAdapterStack.push(adapter);
+        setTitleAndSwitchToAdapter(adapter);
     }
 
     /**
      * Close the drawer if open.
      */
     protected void closeDrawer() {
-        mDrawerLayout.closeDrawer(Gravity.LEFT);
+        if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+            mDrawerLayout.closeDrawer(Gravity.LEFT);
+        }
+    }
+
+    /**
+     * Used to switch between the Drawer PagedListView and the "loading" progress-bar while the next
+     * level's adapter contents are being fetched.
+     *
+     * @param enable If true, the progress-bar is displayed. If false, the Drawer PagedListView is
+     *               added.
+     */
+    protected void showLoadingProgressBar(boolean enable) {
+        mDrawerList.setVisibility(enable ? View.INVISIBLE : View.VISIBLE);
+        mProgressBar.setVisibility(enable ? View.VISIBLE : View.GONE);
     }
 
     /**
@@ -127,7 +154,11 @@
             @Override
             public void onDrawerOpened(View drawerView) {}
             @Override
-            public void onDrawerClosed(View drawerView) {}
+            public void onDrawerClosed(View drawerView) {
+                // If drawer is closed for any reason, revert stack/drawer to initial root state.
+                cleanupStackAndShowRoot();
+                mDrawerList.getRecyclerView().scrollToPosition(0);
+            }
             @Override
             public void onDrawerStateChanged(int newState) {}
         });
@@ -166,11 +197,41 @@
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        // Delegate touches; It toobar handled them (e.g. menu icon tap), we return.
+        // Handle home-click and see if we can navigate up in the drawer.
+        if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
+            return true;
+        }
+
+        // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
         if (mDrawerToggle.onOptionsItemSelected(item)) {
             return true;
         }
 
         return super.onOptionsItemSelected(item);
     }
+
+    private void setTitleAndSwitchToAdapter(CarDrawerAdapter adapter) {
+        mToolbar.setTitle(adapter.getTitleResId());
+        // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
+        // car_menu_list_item_normal, car_menu_list_item_small and car_list_empty layouts.
+        mDrawerList.getRecyclerView().setAdapter(adapter);
+        mDrawerList.getRecyclerView().scrollToPosition(0);
+    }
+
+    private boolean maybeHandleUpClick() {
+        if (mAdapterStack.size() > 1) {
+            mAdapterStack.pop();
+            setTitleAndSwitchToAdapter(mAdapterStack.peek());
+            return true;
+        }
+        return false;
+    }
+
+    /** Clears stack down to root adapter and switches to root adapter. */
+    private void cleanupStackAndShowRoot() {
+        while (mAdapterStack.size() > 1) {
+            mAdapterStack.pop();
+        }
+        setTitleAndSwitchToAdapter(mAdapterStack.peek());
+    }
 }
diff --git a/car-stream-ui-lib/src/com/android/car/app/CarDrawerAdapter.java b/car-stream-ui-lib/src/com/android/car/app/CarDrawerAdapter.java
new file mode 100644
index 0000000..166da14
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/app/CarDrawerAdapter.java
@@ -0,0 +1,42 @@
+package com.android.car.app;
+
+import android.support.annotation.StringRes;
+import android.support.car.ui.PagedListView;
+import android.support.v7.widget.RecyclerView;
+
+/**
+ * Base Adapter for displaying items in the CarDrawerActivity's Drawer which is a PagedListView.
+ * <p>
+ * Implementors must return the string resource for the title that will be displayed when displaying
+ * the contents of this adapter (see {@link #getTitleResId()}.
+ * <p>
+ * This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
+ * should implement {@link #getActualItemCount()}.
+ */
+public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
+        implements PagedListView.ItemCap {
+
+    private int mMaxItems = -1;
+
+    @Override
+    public final void setMaxItems(int maxItems) {
+        mMaxItems = maxItems;
+    }
+
+    @Override
+    public final int getItemCount() {
+        return mMaxItems >= 0  ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
+    }
+
+    /**
+     * @return Actual number of items in this adapter.
+     */
+    protected abstract int getActualItemCount();
+
+    /**
+     * @return String resource to display in the toolbar title when displaying this adapter's
+     * contents.
+     */
+    @StringRes
+    protected abstract int getTitleResId();
+}
diff --git a/car-stream-ui-lib/src/com/android/car/app/CarDrawerEmptyAdapter.java b/car-stream-ui-lib/src/com/android/car/app/CarDrawerEmptyAdapter.java
new file mode 100644
index 0000000..99c403b
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/app/CarDrawerEmptyAdapter.java
@@ -0,0 +1,51 @@
+package com.android.car.app;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.StringRes;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.car.stream.ui.R;
+
+/**
+ * Concrete subclass of {@link CarDrawerAdapter} to that displays a single "empty list" indicator.
+ */
+public class CarDrawerEmptyAdapter extends CarDrawerAdapter {
+    @StringRes
+    private final int mTitleResId;
+    private final Drawable mEmptyListDrawable;
+
+    public CarDrawerEmptyAdapter(Context context, @StringRes int titleResId) {
+        mTitleResId = titleResId;
+        final int iconColor = context.getColor(R.color.car_tint);
+        mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
+        mEmptyListDrawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
+    }
+
+    @Override
+    public DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.car_list_item_empty, parent, false);
+        return new DrawerItemViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+        holder.getTitle().setText(null);
+        holder.getIcon().setImageDrawable(mEmptyListDrawable);
+        holder.setItemClickListener(null);
+    }
+
+    @Override
+    protected int getActualItemCount() {
+        return 1;
+    }
+
+    @Override
+    protected int getTitleResId() {
+        return mTitleResId;
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/app/CarDrawerListAdapter.java b/car-stream-ui-lib/src/com/android/car/app/CarDrawerListAdapter.java
new file mode 100644
index 0000000..7a2c052
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/app/CarDrawerListAdapter.java
@@ -0,0 +1,56 @@
+package com.android.car.app;
+
+import android.support.annotation.LayoutRes;
+import android.support.car.ui.PagedListView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.car.stream.ui.R;
+
+/**
+ * Variant of {@link CarDrawerAdapter} for displaying a list of items that support clicks.
+ * <p>
+ * Subclasses should implement:
+ * <ul>
+ *     <li>{@link #populateViewHolder(DrawerItemViewHolder, int)} to actually populate the
+ *     drawer-item.</li>
+ *     <li>{@link #getTitleResId()} to set the string to display when the Drawer is displaying this
+ *     adapter's contents.</li>
+ *     <li>{@link #getActualItemCount()} to return the actual number of items in the adapter (since
+ *     {@link #getItemCount()} needs to honor {@link PagedListView.ItemCap}.</li>
+ *     <li>{@link #onItemClick(int)} to handle clicks on items. To load a sub-level of items, the
+ *     handler may call {@link CarDrawerActivity#switchToAdapter(CarDrawerAdapter)} to load
+ *     the next level of items.</li>
+ * </ul>
+ */
+public abstract class CarDrawerListAdapter extends CarDrawerAdapter
+        implements DrawerItemClickListener {
+    @LayoutRes
+    private final int mItemLayoutResId;
+
+    protected CarDrawerListAdapter(boolean useNormalLayout) {
+        mItemLayoutResId = useNormalLayout ?
+                R.layout.car_menu_list_item_normal : R.layout.car_menu_list_item_small;
+    }
+
+    @Override
+    public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext())
+                .inflate(mItemLayoutResId, parent, false);
+        return new DrawerItemViewHolder(view);
+    }
+
+    @Override
+    public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+        holder.setItemClickListener(this);
+        populateViewHolder(holder, position);
+    }
+
+    /**
+     * Subclasses should set all elements in {@code holder} to populate the drawer-item.
+     * If some element is not used, it should be nulled out since these ViewHolder/View's are
+     * recycled.
+     */
+    protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
+}
\ No newline at end of file
diff --git a/car-stream-ui-lib/src/com/android/car/app/DrawerItemViewHolder.java b/car-stream-ui-lib/src/com/android/car/app/DrawerItemViewHolder.java
index 38524cf..55eedc5 100644
--- a/car-stream-ui-lib/src/com/android/car/app/DrawerItemViewHolder.java
+++ b/car-stream-ui-lib/src/com/android/car/app/DrawerItemViewHolder.java
@@ -16,84 +16,77 @@
 
 package com.android.car.app;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.car.stream.ui.R;
 
 /**
- * Re-usable ViewHolder that inflates car_menu_list_item.xml layout for use in the Drawer
- * PagedListView (see {@link CarDrawerActivity#getDrawerListView()}.
- * <p>
- * Clients should call {@link #create(ViewGroup, DrawerItemClickListener)} in their RecyclerView
- * Adapter's onCreateViewHolder.
- * <p>
- * A {@link DrawerItemClickListener} can be provided to handle clicks on specific items.
+ * Re-usable ViewHolder for displaying items in the Drawer PagedListView.
  */
 public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
     private final ImageView mIcon;
     private final TextView mTitle;
     private final TextView mText;
-    private final ViewStub mRightItem;
+    private final ImageView mRightIcon;
 
-    /**
-     * Inflates car_menu_list_item.xml layout and wraps it in a DrawerItemViewHolder.
-     *
-     * @param parent Parent ViewGroup for created views.
-     * @param listener Optional click listener to handle clicks.
-     * @return DrawerItemViewHolder wrapping the inflated view.
-     */
-    public static DrawerItemViewHolder create(ViewGroup parent,
-            @Nullable DrawerItemClickListener listener) {
-        View view = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.car_menu_list_item, parent, false);
-        return new DrawerItemViewHolder(view, listener);
+    DrawerItemViewHolder(View view) {
+        super(view);
+        mIcon = (ImageView)view.findViewById(R.id.icon);
+        mTitle = (TextView)view.findViewById(R.id.title);
+        if (mIcon == null || mTitle == null) {
+            throw new IllegalArgumentException("Missing required elements in provided view!");
+        }
+        // Next two are optional and may be null.
+        mText = (TextView)view.findViewById(R.id.text);
+        mRightIcon = (ImageView)view.findViewById(R.id.right_icon);
     }
 
     /**
-     * @return Icon ImageView from inflated car_menu_list_item.xml layout.
+     * @return Icon ImageView from inflated layout.
      */
+    @NonNull
     public ImageView getIcon() {
         return mIcon;
     }
 
     /**
-     * @return Main title TextView inflated from car_menu_list_item.xml layout.
+     * @return Main title TextView from inflated layout.
      */
+    @NonNull
     public TextView getTitle() {
         return mTitle;
     }
 
     /**
-     * @return Main text TextView from inflated car_menu_list_item.xml layout.
+     * @return Main text TextView from inflated layout. May be null.
      */
+    @Nullable
     public TextView getText() {
         return mText;
     }
 
     /**
-     * @return Right-Item ViewStub from inflated car_menu_list_item.xml layout.
+     * @return Right-Icon ImageView from inflated layout. May be null.
      */
-    public ViewStub getRightItem() {
-        return mRightItem;
+    @Nullable
+    public ImageView getRightIcon() {
+        return mRightIcon;
     }
 
-    private DrawerItemViewHolder(View view, @Nullable DrawerItemClickListener listener) {
-        super(view);
-        mIcon = (ImageView)view.findViewById(R.id.icon);
-        mTitle = (TextView)view.findViewById(R.id.title);
-        mText = (TextView)view.findViewById(R.id.text);
-        mRightItem = (ViewStub)view.findViewById(R.id.right_item);
+    /**
+     * Set click-listener on the view wrapped by this ViewHolder. For now only used by
+     * {@link CarDrawerListAdapter}.
+     */
+    void setItemClickListener(@Nullable DrawerItemClickListener listener) {
         if (listener != null) {
-            view.setOnClickListener((unusedView) ->  {
-                listener.onItemClick(getAdapterPosition());
-            });
+            itemView.setOnClickListener((unusedView) -> listener.onItemClick(getAdapterPosition()));
+        } else {
+            itemView.setOnClickListener(null);
         }
     }
 }