Break the dependency of car-stream-ui-lib on car support lib.

We need to remove all the ui components from the support lib
so make copies of all the resources and classes needed by
downstream applications.

Bug: 33210228
Test: Built and verified the dialer looks ok. The dialer has
      completely been ported over to the new drawer classes.

Change-Id: I76f0d43db0cd75eb35ec4e2d895e334541fbe2de
diff --git a/car-apps-common/Android.mk b/car-apps-common/Android.mk
index fe9f8eb..86f330d 100644
--- a/car-apps-common/Android.mk
+++ b/car-apps-common/Android.mk
@@ -23,20 +23,16 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_MODULE := car-apps-common
+
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_PRIVILEGED_MODULE := true
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-# Include android-support-v4, if not already included
-ifeq (,$(findstring android-support-v4,$(LOCAL_STATIC_JAVA_LIBRARIES)))
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
-endif
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 \
+        android-support-annotations
 
-# Include android-support-annotations, if not already included
-ifeq (,$(findstring android-support-annotations,$(LOCAL_STATIC_JAVA_LIBRARIES)))
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
-endif
+include packages/apps/Car/libs/car-stream-ui-lib/car-stream-ui-lib.mk
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/car-apps-common/res/anim/car_fab_state_list_animator.xml b/car-apps-common/res/anim/car_fab_state_list_animator.xml
new file mode 100644
index 0000000..a1a2240
--- /dev/null
+++ b/car-apps-common/res/anim/car_fab_state_list_animator.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" >
+        <set>
+            <objectAnimator android:propertyName="translationZ"
+                android:duration="@integer/car_fab_animation_duration"
+                android:valueTo="16dp"
+                android:valueType="floatType"/>
+        </set>
+    </item>
+    <!-- base state -->
+    <item android:state_focused="false">
+        <set>
+            <objectAnimator android:propertyName="translationZ"
+                android:duration="@integer/car_fab_animation_duration"
+                android:valueTo="0dp"
+                android:valueType="floatType"/>
+        </set>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/car-apps-common/res/values/colors.xml b/car-apps-common/res/values/colors.xml
index 9e0617d..201e262 100644
--- a/car-apps-common/res/values/colors.xml
+++ b/car-apps-common/res/values/colors.xml
@@ -33,4 +33,4 @@
         <item>#ff5722</item>
         <item>#757575</item>
     </array>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/car-apps-common/res/values/dimens.xml b/car-apps-common/res/values/dimens.xml
index fa04e6c..b55b66b 100644
--- a/car-apps-common/res/values/dimens.xml
+++ b/car-apps-common/res/values/dimens.xml
@@ -16,4 +16,6 @@
 -->
 <resources>
     <item name="letter_to_tile_ratio" format="fraction" type="fraction">67%</item>
+    <dimen name="car_fab_focused_stroke_width">8dp</dimen>
+    <dimen name="car_fab_focused_growth">1.2dp</dimen>
 </resources>
diff --git a/car-apps-common/res/values/integers.xml b/car-apps-common/res/values/integers.xml
new file mode 100644
index 0000000..0d5bb5f
--- /dev/null
+++ b/car-apps-common/res/values/integers.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources>
+    <integer name="car_fab_animation_duration">150</integer>
+</resources>
diff --git a/car-apps-common/src/com/android/car/apps/common/CircleBitmapDrawable.java b/car-apps-common/src/com/android/car/apps/common/CircleBitmapDrawable.java
index 87f6728..35d8f91 100644
--- a/car-apps-common/src/com/android/car/apps/common/CircleBitmapDrawable.java
+++ b/car-apps-common/src/com/android/car/apps/common/CircleBitmapDrawable.java
@@ -1,18 +1,19 @@
 /*
- * Copyright (c) 2016, The Android Open Source Project
+ * 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
+ *      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.
- */package com.android.car.apps.common;
+ */
+package com.android.car.apps.common;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -25,12 +26,14 @@
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 
+
 /**
  * A drawable for displaying a circular bitmap. This is a wrapper over RoundedBitmapDrawable,
  * since that implementation doesn't behave quite as desired.
  *
  * Note that not all drawable functionality is passed to the RoundedBitmapDrawable at this
  * time. Feel free to add more as necessary.
+ * @hide
  */
 public class CircleBitmapDrawable extends Drawable {
     private final Resources mResources;
@@ -52,19 +55,6 @@
         int height = bounds.bottom - bounds.top;
 
         Bitmap processed = mBitmap;
-        if (processed.getWidth() != width || processed.getHeight() != height) {
-            processed = BitmapUtils.scaleBitmap(processed, width, height);
-        }
-        // RoundedBitmapDrawable is actually just a rounded rectangle. So it can't turn
-        // rectangular images into circles.
-        if (processed.getWidth() != processed.getHeight()) {
-            int diam = Math.min(width, height);
-            Bitmap cropped = BitmapUtils.cropBitmap(processed, diam, diam);
-            if (processed != mBitmap) {
-                processed.recycle();
-            }
-            processed = cropped;
-        }
         mDrawable = RoundedBitmapDrawableFactory.create(mResources, processed);
         mDrawable.setBounds(bounds);
         mDrawable.setAntiAlias(true);
@@ -123,3 +113,4 @@
         return largeIcon;
     }
 }
+
diff --git a/car-apps-common/src/com/android/car/apps/common/ColorChecker.java b/car-apps-common/src/com/android/car/apps/common/ColorChecker.java
new file mode 100644
index 0000000..e032f9f
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/ColorChecker.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+package com.android.car.apps.common;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class ColorChecker {
+    private static final String TAG = "ColorChecker";
+    private static final double MIN_CONTRAST_RATIO = 4.5;
+    /**
+     * Non-critical information doesn't have to meet as stringent contrast requirements.
+     */
+    private static final double MIN_NON_CRITICAL_CONTRAST_RATIO = 1.5;
+
+    /**
+     * Calls {@link #getTintColor(int, int...)} with:
+     *     {@link R.color#car_tint_light} and
+     *     {@link R.color#car_tint_dark}
+     */
+    public static int getTintColor(Context context, int backgroundColor) {
+        int lightTintColor = context.getResources().getColor(R.color.car_tint_light);
+        int darkTintColor = context.getResources().getColor(R.color.car_tint_dark);
+
+        return getTintColor(backgroundColor, lightTintColor, darkTintColor);
+    }
+
+    /**
+     * Calls {@link #getNonCriticalTintColor(int, int...)} with:
+     *     {@link R.color#car_tint_light} and
+     *     {@link R.color#car_tint_dark}
+     */
+    public static int getNonCriticalTintColor(Context context, int backgroundColor) {
+        int lightTintColor = context.getResources().getColor(R.color.car_tint_light);
+        int darkTintColor = context.getResources().getColor(R.color.car_tint_dark);
+
+        return getNonCriticalTintColor(backgroundColor, lightTintColor, darkTintColor);
+    }
+
+    /**
+     * Calls {@link #getTintColor(int, int...)} with {@link #MIN_CONTRAST_RATIO}.
+     */
+    public static int getTintColor(int backgroundColor, int... tintColors) {
+        return getTintColor(MIN_CONTRAST_RATIO, backgroundColor, tintColors);
+    }
+
+    /**
+     * Calls {@link #getTintColor(int, int...)} with {@link #MIN_NON_CRITICAL_CONTRAST_RATIO}.
+     */
+    public static int getNonCriticalTintColor(int backgroundColor, int... tintColors) {
+        return getTintColor(MIN_NON_CRITICAL_CONTRAST_RATIO, backgroundColor, tintColors);
+    }
+
+    /**
+     *
+     * Determines what color to tint icons given the background color that they sit on.
+     *
+     * @param minAllowedContrastRatio The minimum contrast ratio
+     * @param bgColor The background color that the icons sit on.
+     * @param tintColors A list of potential colors to tint the icons with.
+     * @return The color that the icons should be tinted. Will be the first tinted color that
+     *         meets the requirements. If none of the tint colors meet the minimum requirements,
+     *         either black or white will be returned, whichever has a higher contrast.
+     */
+    public static int getTintColor(double minAllowedContrastRatio, int bgColor, int... tintColors) {
+        for (int tc : tintColors) {
+            double contrastRatio = getContrastRatio(bgColor, tc);
+            if (contrastRatio >= minAllowedContrastRatio) {
+                return tc;
+            }
+        }
+        double blackContrastRatio = getContrastRatio(bgColor, Color.BLACK);
+        double whiteContrastRatio = getContrastRatio(bgColor, Color.WHITE);
+        if (whiteContrastRatio >= blackContrastRatio) {
+            Log.w(TAG, "Tint color does not meet contrast requirements. Using white.");
+            return Color.WHITE;
+        } else {
+            Log.w(TAG, "Tint color does not meet contrast requirements. Using black.");
+            return Color.BLACK;
+        }
+    }
+
+    public static double getContrastRatio(int color1, int color2) {
+        return getContrastRatio(getLuminance(color1), getLuminance(color2));
+    }
+
+    public static double getContrastRatio(double luminance1, double luminance2) {
+        return (Math.max(luminance1, luminance2) + 0.05) /
+                (Math.min(luminance1, luminance2) + 0.05);
+    }
+
+    /**
+     * Calculates the luminance of a color as specified by:
+     *     http://www.w3.org/TR/WCAG20-TECHS/G17.html
+     *
+     * @param color The color to calculate the luminance of.
+     * @return The luminance.
+     */
+    public static double getLuminance(int color) {
+        // Values are in sRGB
+        double r = convert8BitToLuminanceComponent(Color.red(color));
+        double g = convert8BitToLuminanceComponent(Color.green(color));
+        double b = convert8BitToLuminanceComponent(Color.blue(color));
+        return r * 0.2126 + g * 0.7152 + b * 0.0722;
+    }
+
+    /**
+     * Converts am 8 bit color component (0-255) to the luminance component as specified by:
+     *     http://www.w3.org/TR/WCAG20-TECHS/G17.html
+     */
+    private static double convert8BitToLuminanceComponent(double component) {
+        component /= 255.0;
+        if (component <= 0.03928) {
+            return component / 12.92;
+        } else {
+            return Math.pow(((component + 0.055) / 1.055), 2.4);
+        }
+    }
+}
diff --git a/car-apps-common/src/com/android/car/apps/common/FabDrawable.java b/car-apps-common/src/com/android/car/apps/common/FabDrawable.java
new file mode 100644
index 0000000..8e3d5a7
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/FabDrawable.java
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+package com.android.car.apps.common;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * Custom drawable that can be used as the background for fabs.
+ *
+ * When not focused or pressed, the fab will be a solid circle of the color specified with
+ * {@link #setFabColor(int)}. When it is pressed or focused, the fab will grow or shrink
+ * and it will gain a stroke that has the color specified with {@link #setStrokeColor(int)}.
+ *
+ * {@link #FabDrawable(android.content.Context)} provides a quick way to use fab drawable using
+ * default values for size and animation values provided for consistency.
+ *
+ * {@link #FabDrawable(int, int, int)} can also be used for added customization.
+ * @hide
+ */
+public class FabDrawable extends Drawable {
+    private final int mFabGrowth;
+    private final int mStrokeWidth;
+    private final Paint mFabPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final ValueAnimator mStrokeAnimator;
+
+    private boolean mStrokeAnimatorIsReversing;
+    private int mFabRadius;
+    private int mStrokeRadius;
+    private Outline mOutline;
+
+    /**
+     * Default constructor to provide consistent fab values across uses.
+     */
+    public FabDrawable(Context context) {
+        this(context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_growth),
+                context.getResources().getDimensionPixelSize(R.dimen.car_fab_focused_stroke_width),
+                context.getResources().getInteger(R.integer.car_fab_animation_duration));
+    }
+
+    /**
+     * Custom constructor allows extra customization of the fab's behavior.
+     *
+     * @param fabGrowth The amount that the fab should change by when it is focused in pixels.
+     * @param strokeWidth The width of the stroke when the fab is focused in pixels.
+     * @param duration The animation duration for the growth of the fab and stroke.
+     */
+    public FabDrawable(int fabGrowth, int strokeWidth, int duration) {
+        if (fabGrowth < 0) {
+            throw new IllegalArgumentException("Fab growth must be >= 0.");
+        } else if (fabGrowth > strokeWidth) {
+            throw new IllegalArgumentException("Fab growth must be <= strokeWidth.");
+        } else if (strokeWidth < 0) {
+            throw new IllegalArgumentException("Stroke width must be >= 0.");
+        }
+        mFabGrowth = fabGrowth;
+        mStrokeWidth = strokeWidth;
+        mStrokeAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(duration);
+        mStrokeAnimator.setInterpolator(new DecelerateInterpolator());
+        mStrokeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                updateRadius();
+            }
+        });
+    }
+
+    /**
+     * @param color The primary color of the fab. It will be the entire fab color when not selected
+     *              or pressed and will be the color of the interior circle when selected
+     *              or pressed.
+     */
+    public void setFabColor(int color) {
+        mFabPaint.setColor(color);
+    }
+
+    /**
+     * @param color The color of the stroke on the fab that appears when the fab is selected
+     *              or pressed.
+     */
+    public void setStrokeColor(int color) {
+        mStrokePaint.setColor(color);
+    }
+
+    /**
+     * Default implementation of {@link #setFabAndStrokeColor(int, float)} with valueMultiplier
+     * set to 0.9.
+     */
+    public void setFabAndStrokeColor(int color) {
+        setFabAndStrokeColor(color, 0.9f);
+    }
+
+    /**
+     * @param color The primary color of the fab.
+     * @param valueMultiplier The hsv value multiplier that will be set as the stroke color.
+     */
+    public void setFabAndStrokeColor(int color, float valueMultiplier) {
+        setFabColor(color);
+        float[] hsv = new float[3];
+        Color.colorToHSV(color, hsv);
+        hsv[2] *= valueMultiplier;
+        setStrokeColor(Color.HSVToColor(hsv));
+    }
+
+    @Override
+    protected boolean onStateChange(int[] stateSet) {
+        boolean superChanged = super.onStateChange(stateSet);
+
+        boolean focused = false;
+        boolean pressed = false;
+
+        for (int state : stateSet) {
+            if (state == android.R.attr.state_focused) {
+                focused = true;
+            } else if (state == android.R.attr.state_pressed) {
+                pressed = true;
+            }
+        }
+
+        if ((focused || pressed) && mStrokeAnimatorIsReversing) {
+            mStrokeAnimator.start();
+            mStrokeAnimatorIsReversing = false;
+        } else if (!(focused || pressed) && !mStrokeAnimatorIsReversing) {
+            mStrokeAnimator.reverse();
+            mStrokeAnimatorIsReversing = true;
+        }
+
+        return superChanged || focused;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int cx = canvas.getWidth() / 2;
+        int cy = canvas.getHeight() / 2;
+
+        canvas.drawCircle(cx, cy, mStrokeRadius, mStrokePaint);
+        canvas.drawCircle(cx, cy, mFabRadius, mFabPaint);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        updateRadius();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mFabPaint.setAlpha(alpha);
+        mStrokePaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mFabPaint.setColorFilter(colorFilter);
+        mStrokePaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        mOutline = outline;
+        updateOutline();
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    private void updateRadius() {
+        int normalRadius = Math.min(getBounds().width(), getBounds().height()) / 2 - mStrokeWidth;
+        float fraction = mStrokeAnimator.getAnimatedFraction();
+        mStrokeRadius = (int) (normalRadius + (mStrokeWidth * fraction));
+        mFabRadius = (int) (normalRadius + (mFabGrowth * fraction));
+        updateOutline();
+        invalidateSelf();
+    }
+
+    private void updateOutline() {
+        int cx = getBounds().width() / 2;
+        int cy = getBounds().height() / 2;
+        if (mOutline != null) {
+            mOutline.setRoundRect(
+                    cx - mStrokeRadius,
+                    cy - mStrokeRadius,
+                    cx + mStrokeRadius,
+                    cy + mStrokeRadius,
+                    mStrokeRadius);
+        }
+    }
+}
diff --git a/car-stream-ui-lib/Android.mk b/car-stream-ui-lib/Android.mk
index 3a43627..2fb2a97 100644
--- a/car-stream-ui-lib/Android.mk
+++ b/car-stream-ui-lib/Android.mk
@@ -20,28 +20,24 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+        frameworks/support/v7/appcompat/res \
+        frameworks/support/v7/cardview/res \
+        frameworks/support/v7/recyclerview/res
 
-# Include support-v7-cardview, if not already included
-ifeq (,$(findstring android-support-v7-cardview,$(LOCAL_STATIC_JAVA_LIBRARIES)))
-LOCAL_RESOURCE_DIR += frameworks/support/v7/cardview/res
-LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.cardview
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-cardview
-endif
-
-# Include support-v7-appcompat, if not already included
-ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES)))
-LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
-LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
-endif
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations \
+        android-support-v4 \
+        android-support-v7-appcompat \
+        android-support-v7-cardview \
+        android-support-v7-recyclerview
 
 LOCAL_MODULE := car-stream-ui-lib
 LOCAL_MODULE_TAGS := optional
-LOCAL_AAPT_FLAGS += --auto-add-overlay
+LOCAL_AAPT_FLAGS += --auto-add-overlay \
+        --extra-packages android.support.v7.appcompat \
+        --extra-packages android.support.v7.cardview \
+        --extra-packages android.support.v7.recyclerview
 
 LOCAL_PROGUARD_ENABLED := disabled
 
-include packages/services/Car/car-support-lib/car-support.mk
-
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/car-stream-ui-lib/car-stream-ui-lib.mk b/car-stream-ui-lib/car-stream-ui-lib.mk
index b0d7c1d..86ad7a6 100644
--- a/car-stream-ui-lib/car-stream-ui-lib.mk
+++ b/car-stream-ui-lib/car-stream-ui-lib.mk
@@ -33,22 +33,27 @@
 $(error LOCAL_RESOURCE_DIR must be defined)
 endif
 
+# Add --auto-add-overlay flag if not present
+ifeq (,$(findstring --auto-add-overlay, $(LOCAL_AAPT_FLAGS)))
+LOCAL_AAPT_FLAGS += --auto-add-overlay
+endif
+
 # Include support-v7-cardview, if not already included
 ifeq (,$(findstring android-support-v7-cardview,$(LOCAL_STATIC_JAVA_LIBRARIES)))
 LOCAL_RESOURCE_DIR += frameworks/support/v7/cardview/res
 LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.cardview
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-cardview
 endif
+
 # Include support-v7-appcompat, if not already included
 ifeq (,$(findstring android-support-v7-appcompat,$(LOCAL_STATIC_JAVA_LIBRARIES)))
 LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
 LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
 endif
-LOCAL_JAVA_LIBRARIES += android.car
 
 # Include car-stream-ui-lib
-ifeq (,$(findstring android.support.car, $(LOCAL_STATIC_JAVA_LIBRARIES)))
+ifeq (,$(findstring car-stream-ui-lib, $(LOCAL_STATIC_JAVA_LIBRARIES)))
 LOCAL_RESOURCE_DIR += \
     packages/apps/Car/libs/car-stream-ui-lib/res
 LOCAL_AAPT_FLAGS += --extra-packages com.android.car.stream.ui
diff --git a/car-stream-ui-lib/res/drawable/car_list_item_background.xml b/car-stream-ui-lib/res/drawable/car_list_item_background.xml
new file mode 100644
index 0000000..9f6863f
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/car_list_item_background.xml
@@ -0,0 +1,20 @@
+<!-- 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background">
+    <item android:id="@android:id/mask">
+        <color android:color="#ffffffff" />
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/drawable/car_pagination_background.xml b/car-stream-ui-lib/res/drawable/car_pagination_background.xml
new file mode 100644
index 0000000..7f72c8b
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/car_pagination_background.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background" />
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/drawable/car_pagination_background_dark.xml b/car-stream-ui-lib/res/drawable/car_pagination_background_dark.xml
new file mode 100644
index 0000000..8ed8c1a
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/car_pagination_background_dark.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_dark" />
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/drawable/car_pagination_background_light.xml b/car-stream-ui-lib/res/drawable/car_pagination_background_light.xml
new file mode 100644
index 0000000..c109739
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/car_pagination_background_light.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_light" />
+
diff --git a/car-stream-ui-lib/res/drawable/ic_chevron_right.xml b/car-stream-ui-lib/res/drawable/ic_chevron_right.xml
new file mode 100644
index 0000000..89557b6
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/ic_chevron_right.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#212121"
+        android:strokeWidth="1"
+        android:pathData="M 10 6 L 8.59 7.41 L 13.17 12 L 8.59 16.59 L 10 18 L 16 12 Z" />
+    <path
+        android:strokeWidth="1"
+        android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" />
+</vector>
diff --git a/car-stream-ui-lib/res/drawable/ic_down_button.xml b/car-stream-ui-lib/res/drawable/ic_down_button.xml
new file mode 100644
index 0000000..0565a63
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/ic_down_button.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<vector android:height="24dp" android:viewportHeight="48.0"
+    android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M24,0c-13.3,0 -24,10.7 -24,24s10.7,24 24,24s24,-10.7 24,-24S37.3,0 24,0zM24,46c-12.1,0 -22,-9.9 -22,-22s9.9,-22 22,-22s22,9.9 22,22S36.1,46 24,46z"/>
+    <path android:fillColor="#FF000000" android:pathData="M17.1,19.2l6.9,6.9l6.9,-6.9l2.1,2.1l-9,9.1l-9,-9.1z"/>
+</vector>
diff --git a/car-stream-ui-lib/res/drawable/ic_up_button.xml b/car-stream-ui-lib/res/drawable/ic_up_button.xml
new file mode 100644
index 0000000..8ac579a
--- /dev/null
+++ b/car-stream-ui-lib/res/drawable/ic_up_button.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<vector android:height="24dp" android:viewportHeight="48.0"
+    android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M24,48c13.3,0 24,-10.7 24,-24s-10.7,-24 -24,-24s-24,10.7 -24,24S10.7,48 24,48zM24,2c12.1,0 22,9.9 22,22s-9.9,22 -22,22s-22,-9.9 -22,-22S11.9,2 24,2z"/>
+    <path android:fillColor="#FF000000" android:pathData="M30.9,28.8l-6.9,-6.9l-6.9,6.9l-2.1,-2.1l9,-9.1l9,9.1z"/>
+</vector>
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 88524e0..4e58182 100644
--- a/car-stream-ui-lib/res/layout/car_drawer_activity.xml
+++ b/car-stream-ui-lib/res/layout/car_drawer_activity.xml
@@ -39,7 +39,7 @@
             android:background="@color/car_card"
             android:paddingTop="@dimen/lens_header_height" >
 
-            <android.support.car.ui.PagedListView
+            <com.android.car.view.PagedListView
                 android:id="@+id/drawer_list"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" />
@@ -68,4 +68,4 @@
             app:contentInsetStart="@dimen/stream_content_keyline_1" />
     </FrameLayout>
 
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/car-stream-ui-lib/res/layout/car_imageview.xml b/car-stream-ui-lib/res/layout/car_imageview.xml
new file mode 100644
index 0000000..ac51118
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_imageview.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/car_list_item_right_icon_size"
+    android:layout_height="@dimen/car_list_item_right_icon_size"
+    android:scaleType="fitCenter" />
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/layout/car_list_item_1.xml b/car-stream-ui-lib/res/layout/car_list_item_1.xml
new file mode 100644
index 0000000..0bd9ee9
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_1.xml
@@ -0,0 +1,45 @@
+<?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"
+    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:scaleType="centerCrop"
+        android:layout_gravity="center_vertical" />
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        style="@style/CarBody1"
+        android:singleLine="true" />
+    <ImageView
+        android:id="@+id/right_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginRight="@dimen/car_list_item_right_icon_margin"
+        android:scaleType="center" />
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_list_item_1_card.xml b/car-stream-ui-lib/res/layout/car_list_item_1_card.xml
new file mode 100644
index 0000000..3deca15
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_1_card.xml
@@ -0,0 +1,54 @@
+<?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.
+-->
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_list_item_height"
+    android:layout_marginBottom="@dimen/car_card_bottom_margin"
+    android:focusable="true"
+    android:orientation="horizontal" >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/car_list_item_background"
+        android:duplicateParentState="true"
+        android:orientation="horizontal" >
+        <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_marginLeft="16dp"
+            android:layout_marginRight="16dp"
+            android:scaleType="centerCrop"
+            android:layout_gravity="center_vertical" />
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="center_vertical"
+            style="@style/CarBody1"
+            android:singleLine="true" />
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginRight="@dimen/car_list_item_right_icon_margin"
+            android:scaleType="center" />
+    </LinearLayout>
+</android.support.v7.widget.CardView>
diff --git a/car-stream-ui-lib/res/layout/car_list_item_1_small.xml b/car-stream-ui-lib/res/layout/car_list_item_1_small.xml
new file mode 100644
index 0000000..526070e
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_1_small.xml
@@ -0,0 +1,70 @@
+<?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"
+    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" >
+    <FrameLayout
+        android:id="@+id/icon_container"
+        android:layout_width="@dimen/car_list_item_small_icon_size"
+        android:layout_height="@dimen/car_list_item_small_icon_size"
+        android:layout_marginRight="40dp"
+        android:visibility="visible"
+        android:layout_gravity="center_vertical">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:visibility="gone"/>
+    </FrameLayout>
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginRight="8dp"
+        android:layout_gravity="center_vertical"
+        android:ellipsize="end"
+        android:singleLine="true"
+        style="@style/CarBody1" />
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal" >
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="275dp"
+            android:layout_gravity="center_vertical"
+            android:gravity="right"
+            style="@style/CarCaption"
+            android:ellipsize="end"
+            android:singleLine="true" />
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:visibility="gone"
+            android:scaleType="center" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_list_item_1_small_card.xml b/car-stream-ui-lib/res/layout/car_list_item_1_small_card.xml
new file mode 100644
index 0000000..2d90f3c
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_1_small_card.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+<android.support.v7.widget.CardView
+    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"
+     >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/car_list_item_background"
+        android:duplicateParentState="true"
+        android:orientation="horizontal" >
+        <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="40dp"
+            android:layout_gravity="center_vertical"
+            android:visibility="gone"
+            android:scaleType="centerCrop" />
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginRight="8dp"
+            android:layout_gravity="center_vertical"
+            android:ellipsize="end"
+            android:singleLine="true"
+            style="@style/CarBody1" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:orientation="horizontal" >
+            <TextView
+                android:id="@+id/text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:maxWidth="275dp"
+                android:layout_gravity="center_vertical"
+                android:gravity="right"
+                style="@style/CarCaption"
+                android:ellipsize="end"
+                android:singleLine="true" />
+            <ImageView
+                android:id="@+id/right_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginRight="@dimen/car_list_item_right_icon_margin"
+                android:visibility="gone"
+                android:scaleType="center" />
+        </LinearLayout>
+    </LinearLayout>
+</android.support.v7.widget.CardView>
diff --git a/car-stream-ui-lib/res/layout/car_list_item_2.xml b/car-stream-ui-lib/res/layout/car_list_item_2.xml
new file mode 100644
index 0000000..9cf7179
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_2.xml
@@ -0,0 +1,65 @@
+<?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"
+    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" >
+    <FrameLayout
+        android:id="@+id/icon_container"
+        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">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
+    </FrameLayout>
+    <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:gravity="end"
+            android:singleLine="true" />
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/right_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginRight="@dimen/car_list_item_right_icon_margin"
+        android:scaleType="center" />
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_list_item_2_card.xml b/car-stream-ui-lib/res/layout/car_list_item_2_card.xml
new file mode 100644
index 0000000..88a2c5f
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_list_item_2_card.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_list_item_height"
+    android:layout_marginBottom="@dimen/car_card_bottom_margin"
+    android:focusable="true"
+    android:orientation="horizontal"
+     >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/car_list_item_background"
+        android:duplicateParentState="true"
+        android:orientation="horizontal" >
+        <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_marginLeft="16dp"
+            android:layout_marginRight="16dp"
+            android:scaleType="centerCrop"
+            android:layout_gravity="center_vertical" />
+        <LinearLayout
+            android:id="@+id/text_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:layout_gravity="center_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:singleLine="true" />
+        </LinearLayout>
+        <ImageView
+            android:id="@+id/right_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginRight="@dimen/car_list_item_right_icon_margin"
+            android:scaleType="center" />
+    </LinearLayout>
+</android.support.v7.widget.CardView>
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
index 0aaff22..de0e727 100644
--- a/car-stream-ui-lib/res/layout/car_list_item_empty.xml
+++ b/car-stream-ui-lib/res/layout/car_list_item_empty.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- 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.
@@ -18,22 +18,28 @@
     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:layout_marginLeft="16dp"
     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" />
+    <FrameLayout
+        android:id="@+id/icon_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="visible">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginTop="48dp"
+            android:layout_marginBottom="22dp" />
+    </FrameLayout>
     <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:layout_marginEnd="16dp"
         android:gravity="center"
         style="@style/CarBody1" />
 </LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_menu_checkbox.xml b/car-stream-ui-lib/res/layout/car_menu_checkbox.xml
new file mode 100644
index 0000000..777c032
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_menu_checkbox.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<com.android.car.view.CheckboxWrapperView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:focusable="false"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:buttonTint="@color/car_tint"
+    android:buttonTintMode="src_atop" />
diff --git a/car-stream-ui-lib/res/layout/car_menu_list_item.xml b/car-stream-ui-lib/res/layout/car_menu_list_item.xml
new file mode 100644
index 0000000..9cc3ebf
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_menu_list_item.xml
@@ -0,0 +1,75 @@
+<?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"
+    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" >
+    <FrameLayout
+        android:id="@+id/icon_container"
+        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">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
+    </FrameLayout>
+    <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" />
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <FrameLayout
+                android:id="@+id/remoteviews"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:visibility="gone" />
+            <TextView
+                android:id="@+id/text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/CarBody2"
+                android:ellipsize="end"
+                android:gravity="end"
+                android:singleLine="true" />
+        </LinearLayout>
+    </LinearLayout>
+    <ViewStub
+        android:id="@+id/right_item"
+        android:layout_width="@dimen/car_list_item_right_icon_size"
+        android:layout_height="@dimen/car_list_item_right_icon_size"
+        android:layout_marginEnd="32dp"
+        android:layout_gravity="center_vertical" />
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_paged_recycler_view.xml b/car-stream-ui-lib/res/layout/car_paged_recycler_view.xml
new file mode 100644
index 0000000..4045c3d
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_paged_recycler_view.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       android:layout_width="match_parent"
+       android:layout_height="match_parent">
+    <com.android.car.view.MaxWidthLayout
+            android:id="@+id/max_width_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginStart="@dimen/car_drawer_button_container_width">
+        <com.android.car.view.CarRecyclerView
+                android:id="@+id/recycler_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center_horizontal"
+                android:clipChildren="false"/>
+    </com.android.car.view.MaxWidthLayout>
+    <!-- The scroll bar should be drawn ontop of the centered recycler view-->
+    <FrameLayout
+            android:layout_width="@dimen/car_drawer_button_container_width"
+            android:layout_height="match_parent">
+        <com.android.car.view.PagedScrollBarView
+                android:id="@+id/paged_scroll_view"
+                android:layout_width="@dimen/car_paged_list_view_pagination_width"
+                android:layout_height="match_parent"
+                android:paddingBottom="16dp"
+                android:paddingTop="16dp"
+                android:layout_gravity="center_horizontal"
+                android:visibility="invisible"/>
+    </FrameLayout>
+</merge>
diff --git a/car-stream-ui-lib/res/layout/car_paged_scrollbar_buttons.xml b/car-stream-ui-lib/res/layout/car_paged_scrollbar_buttons.xml
new file mode 100644
index 0000000..e976f1c
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_paged_scrollbar_buttons.xml
@@ -0,0 +1,53 @@
+<?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"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="left"
+    android:gravity="center"
+    android:orientation="vertical" >
+    <ImageView
+        android:id="@+id/page_up"
+        android:layout_width="@dimen/scroll_button_size"
+        android:layout_height="@dimen/scroll_button_size"
+        android:scaleType="fitCenter"
+        android:background="@drawable/car_pagination_background"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false" />
+    <FrameLayout
+        android:id="@+id/filler"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:layout_marginTop="@dimen/car_paged_list_view_scrollbar_thumb_margin"
+        android:layout_marginBottom="@dimen/car_paged_list_view_scrollbar_thumb_margin" >
+        <ImageView
+            android:id="@+id/scrollbar_thumb"
+            android:layout_width="@dimen/scroll_bar_thumb_width"
+            android:layout_height="0dp"
+            android:layout_gravity="center_horizontal"
+            android:src="@color/car_scrollbar_thumb" />
+    </FrameLayout>
+    <ImageView
+        android:id="@+id/page_down"
+        android:layout_width="@dimen/scroll_button_size"
+        android:layout_height="@dimen/scroll_button_size"
+        android:scaleType="fitCenter"
+        android:background="@drawable/car_pagination_background"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false" />
+</LinearLayout>
diff --git a/car-stream-ui-lib/res/layout/car_pagination_background_light.xml b/car-stream-ui-lib/res/layout/car_pagination_background_light.xml
new file mode 100644
index 0000000..c109739
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_pagination_background_light.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_light" />
+
diff --git a/car-stream-ui-lib/res/layout/car_textview.xml b/car-stream-ui-lib/res/layout/car_textview.xml
new file mode 100644
index 0000000..5810af7
--- /dev/null
+++ b/car-stream-ui-lib/res/layout/car_textview.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/text"
+      style="@style/CarCaption"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_vertical"
+      android:maxWidth="275dp"
+      android:gravity="right"
+      android:ellipsize="end"
+      android:singleLine="true" />
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/values-h600dp/dimens.xml b/car-stream-ui-lib/res/values-h600dp/dimens.xml
index 955f8d3..2556061 100644
--- a/car-stream-ui-lib/res/values-h600dp/dimens.xml
+++ b/car-stream-ui-lib/res/values-h600dp/dimens.xml
@@ -14,6 +14,30 @@
 limitations under the License.
 -->
 <resources>
+    <dimen name="car_drawer_margin_right">320dp</dimen>
+    <dimen name="car_list_item_right_icon_margin">112dp</dimen>
+
+    <dimen name="car_list_item_icon_right_margin">60dp</dimen>
+
+    <dimen name="car_headline0_size">72sp</dimen>
+    <dimen name="car_headline1_size">56sp</dimen>
+    <dimen name="car_headline2_size">50sp</dimen>
+    <dimen name="car_title_size">32sp</dimen>
+    <dimen name="car_body1_size">40sp</dimen>
+    <dimen name="car_body2_size">32sp</dimen>
+    <dimen name="car_key1_size">50sp</dimen>
+    <dimen name="car_key2_size">22sp</dimen>
+    <dimen name="car_caption_size">22sp</dimen>
+
+    <dimen name="car_card_margin">228dp</dimen>
+
+    <dimen name="car_list_item_icon_size">108dp</dimen>
+    <dimen name="car_list_item_small_icon_size">56dp</dimen>
+    <dimen name="car_list_item_right_icon_size">56dp</dimen>
+
+    <dimen name="car_list_item_height">128dp</dimen>
+    <dimen name="car_list_item_height_small">128dp</dimen>
+
     <dimen name="lens_header_height">148dp</dimen>
     <dimen name="action_panel_height">192dp</dimen>
     <dimen name="stream_fab_size">160dp</dimen>
diff --git a/car-stream-ui-lib/res/values-h600dp/styles.xml b/car-stream-ui-lib/res/values-h600dp/styles.xml
new file mode 100644
index 0000000..831d3ca
--- /dev/null
+++ b/car-stream-ui-lib/res/values-h600dp/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<resources>
+    <style name="CarDrawerArrowDrawable" >
+        <item name="carArrowColor">@android:color/white</item>
+        <item name="carArrowSpinBars">true</item>
+        <item name="carArrowThickness">4dp</item>
+        <item name="carArrowDrawableSize">@dimen/car_drawer_header_menu_button_size</item>
+        <item name="carArrowTopBottomBarSize">26dp</item>
+        <item name="carArrowBarSize">42dp</item>
+        <item name="carArrowMiddleBarSize">35dp</item>
+        <item name="carArrowGapBetweenBars">7dp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/values-night/colors.xml b/car-stream-ui-lib/res/values-night/colors.xml
new file mode 100644
index 0000000..0ecdb84
--- /dev/null
+++ b/car-stream-ui-lib/res/values-night/colors.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<resources>
+    <!-- Aliases for material colors that may be used programmatically -->
+    <color name="car_headline0">@color/car_headline0_light</color>
+    <color name="car_headline1">@color/car_headline1_light</color>
+    <color name="car_headline2">@color/car_headline2_light</color>
+    <color name="car_title">@color/car_title_light</color>
+    <color name="car_body1">@color/car_body1_light</color>
+    <color name="car_body2">@color/car_body2_light</color>
+    <color name="car_key1">@color/car_key1_light</color>
+    <color name="car_key2">@color/car_key2_light</color>
+    <color name="car_caption">@color/car_caption_light</color>
+    <color name="car_micro">@color/car_micro_light</color>
+    <color name="car_card">@color/car_card_dark</color>
+    <color name="car_card_ripple_background">@color/car_card_ripple_background_light</color>
+    <color name="car_card_ripple_light_color_background">@color/car_card_ripple_light_color_background_light</color>
+    <color name="car_overscroll_glow">@color/car_overscroll_glow_dark</color>
+    <color name="car_tint">@color/car_tint_light</color>
+    <color name="car_list_divider">@color/car_white_1000</color>
+    <color name="car_unavailable_category">@color/car_grey_50</color>
+</resources>
diff --git a/car-stream-ui-lib/res/values-w720dp/dimens.xml b/car-stream-ui-lib/res/values-w720dp/dimens.xml
new file mode 100644
index 0000000..d72006e
--- /dev/null
+++ b/car-stream-ui-lib/res/values-w720dp/dimens.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<resources>
+    <dimen name="car_paged_list_view_pagination_width">76dp</dimen>
+    <dimen name="scroll_button_size">76dp</dimen>
+    <dimen name="scroll_bar_thumb_width">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car-stream-ui-lib/res/values-w840dp/dimens.xml b/car-stream-ui-lib/res/values-w840dp/dimens.xml
index a3333d2..a735774 100644
--- a/car-stream-ui-lib/res/values-w840dp/dimens.xml
+++ b/car-stream-ui-lib/res/values-w840dp/dimens.xml
@@ -14,6 +14,7 @@
 limitations under the License.
 -->
 <resources>
+    <dimen name="car_drawer_button_container_width">@dimen/stream_content_keyline_1</dimen>
     <dimen name="stream_margin_size">40dp</dimen>
     <dimen name="stream_gutter_size">24dp</dimen>
     <dimen name="stream_content_keyline_1">40dp</dimen>
diff --git a/car-stream-ui-lib/res/values/attrs.xml b/car-stream-ui-lib/res/values/attrs.xml
index e44672e..d86b8f9 100644
--- a/car-stream-ui-lib/res/values/attrs.xml
+++ b/car-stream-ui-lib/res/values/attrs.xml
@@ -14,9 +14,42 @@
 limitations under the License.
 -->
 <resources>
+    <declare-styleable name="PagedListView">
+        <!-- Fade duration in ms -->
+        <attr name="fadeLastItem" format="boolean" />
+        <!-- Set to true/false to offset rows as they slide off screen. Defaults to true -->
+        <attr name="offsetRows" format="boolean" />
+        <attr name="glowColor" format="color" />
+        <!-- Whether loading list view in drawer or not -->
+        <attr name="rightGutterEnabled" format="boolean" />
+    </declare-styleable>
+
     <declare-styleable name="StreamCardView">
         <!-- The number of columns that this StreamCardView should span across. This value will
              determine the width of the StreamCardView. -->
         <attr name="columnSpan" format="integer" />
     </declare-styleable>
+
+    <declare-styleable name="DrawerArrowDrawable">
+        <!-- The drawing color for the bars -->
+        <attr name="carArrowColor" format="color"/>
+        <!-- Whether bars should rotate or not during transition -->
+        <attr name="carArrowSpinBars" format="boolean"/>
+        <!-- The total size of the drawable -->
+        <attr name="carArrowDrawableSize" format="dimension"/>
+        <!-- The max gap between the bars when they are parallel to each other -->
+        <attr name="carArrowGapBetweenBars" format="dimension"/>
+        <!-- The size of the top and bottom bars when they merge to the middle bar to form an arrow -->
+        <attr name="carArrowTopBottomBarSize" format="dimension"/>
+        <!-- The size of the middle bar when top and bottom bars merge into middle bar to form an arrow -->
+        <attr name="carArrowMiddleBarSize" format="dimension"/>
+        <!-- The size of the bars when they are parallel to each other -->
+        <attr name="carArrowBarSize" format="dimension"/>
+        <!-- The thickness (stroke size) for the bar paint -->
+        <attr name="carArrowThickness" format="dimension"/>
+    </declare-styleable>
+
+    <declare-styleable name="MaxWidthLayout">
+        <attr name="carMaxWidth" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/car-stream-ui-lib/res/values/bools.xml b/car-stream-ui-lib/res/values/bools.xml
new file mode 100644
index 0000000..5552260
--- /dev/null
+++ b/car-stream-ui-lib/res/values/bools.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<resources>
+    <bool name="car_true_for_touch">true</bool>
+    <bool name="has_wheel">false</bool>
+</resources>
diff --git a/car-stream-ui-lib/res/values/colors.xml b/car-stream-ui-lib/res/values/colors.xml
new file mode 100644
index 0000000..ccf22aa
--- /dev/null
+++ b/car-stream-ui-lib/res/values/colors.xml
@@ -0,0 +1,142 @@
+<?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.
+-->
+<resources>
+    <!-- Copy of material colors. -->
+    <!-- These colors are from http://www.google.com/design/spec/style/color.html#color-ui-color-palette -->
+    <color name="car_grey_50">#fffafafa</color>
+    <color name="car_grey_100">#fff5f5f5</color>
+    <color name="car_grey_200">#ffeeeeee</color>
+    <color name="car_grey_300">#ffe0e0e0</color>
+    <color name="car_grey_400">#ffbdbdbd</color>
+    <color name="car_grey_500">#ff9e9e9e</color>
+    <color name="car_grey_600">#ff757575</color>
+    <color name="car_grey_650">#ff6B6B6B</color>
+    <color name="car_grey_700">#ff616161</color>
+    <color name="car_grey_800">#ff424242</color>
+    <color name="car_grey_900">#ff212121</color>
+    <color name="car_grey_1000">#cc000000</color>
+    <color name="car_white_1000">#1effffff</color>
+    <color name="car_blue_grey_800">#ff37474F</color>
+    <color name="car_blue_grey_900">#ff263238</color>
+    <color name="car_dark_blue_grey_600">#ff1d272d</color>
+    <color name="car_dark_blue_grey_700">#ff172026</color>
+    <color name="car_dark_blue_grey_800">#ff11181d</color>
+    <color name="car_dark_blue_grey_900">#ff0c1013</color>
+    <color name="car_dark_blue_grey_1000">#ff090c0f</color>
+    <color name="car_light_blue_300">#ff4fc3f7</color>
+    <color name="car_light_blue_500">#ff03A9F4</color>
+    <color name="car_light_blue_600">#ff039be5</color>
+    <color name="car_light_blue_700">#ff0288d1</color>
+    <color name="car_light_blue_800">#ff0277bd</color>
+    <color name="car_light_blue_900">#ff01579b</color>
+    <color name="car_blue_300">#ff91a7ff</color>
+    <color name="car_blue_500">#ff5677fc</color>
+    <color name="car_green_500">#ff0f9d58</color>
+    <color name="car_green_700">#ff0b8043</color>
+    <color name="car_yellow_500">#fff4b400</color>
+    <color name="car_yellow_800">#ffee8100</color>
+    <color name="car_red_400">#ffe06055</color>
+    <color name="car_red_500">#ffdb4437</color>
+    <color name="car_red_500a">#ffd50000</color>
+    <color name="car_red_700">#ffc53929</color>
+    <color name="car_teal_200">#ff80cbc4</color>
+    <color name="car_teal_700">#ff00796b</color>
+    <color name="car_indigo_800">#ff283593</color>
+    <color name="car_700">#ff172026</color>
+    <color name="car_900">#ff0c1013</color>
+    <color name="car_fab_view_color">#ff009688</color>
+    <color name="car_fab_view_clicked_color">#0026a69a</color>
+    <color name="car_keygroup_circle">#ff104343</color>
+    <!-- Aliases for material colors that may be used programmatically -->
+    <color name="car_headline0_light">@color/car_grey_100</color>
+    <color name="car_headline0_dark">@color/car_grey_800</color>
+    <color name="car_headline0">@color/car_headline0_dark</color>
+
+    <color name="car_headline1_light">@color/car_grey_100</color>
+    <color name="car_headline1_dark">@color/car_grey_800</color>
+    <color name="car_headline1">@color/car_headline1_dark</color>
+
+    <color name="car_headline2_light">@color/car_grey_100</color>
+    <color name="car_headline2_dark">@color/car_grey_900</color>
+    <color name="car_headline2">@color/car_headline2_dark</color>
+
+    <color name="car_title_light">@color/car_grey_100</color>
+    <color name="car_title_dark">@color/car_grey_900</color>
+    <color name="car_title">@color/car_title_dark</color>
+
+    <color name="car_body1_light">@color/car_grey_100</color>
+    <color name="car_body1_dark">@color/car_grey_900</color>
+    <color name="car_body1">@color/car_body1_dark</color>
+
+    <color name="car_body2_dark">@color/car_grey_650</color>
+    <color name="car_body2_light">@color/car_grey_500</color>
+    <color name="car_body2">@color/car_body2_dark</color>
+
+    <color name="car_caption_light">@color/car_grey_400</color>
+    <color name="car_caption_dark">@color/car_grey_700</color>
+    <color name="car_caption">@color/car_caption_dark</color>
+
+    <color name="car_key1_light">@color/car_grey_100</color>
+    <color name="car_key1_dark">@color/car_light_blue_700</color>
+    <color name="car_key1">@color/car_key1_dark</color>
+
+    <color name="car_key2_light">@color/car_grey_400</color>
+    <color name="car_key2_dark">@color/car_grey_650</color>
+    <color name="car_key2">@color/car_key2_dark</color>
+
+    <color name="car_micro_light">@color/car_grey_100</color>
+    <color name="car_micro_dark">@color/car_grey_700</color>
+    <color name="car_micro">@color/car_micro_dark</color>
+
+    <color name="car_card_light">@color/car_grey_50</color>
+    <color name="car_card_dark">@color/car_dark_blue_grey_700</color>
+    <color name="car_card">@color/car_card_light</color>
+
+    <color name="car_card_ripple_background_dark">#17000000</color>
+    <color name="car_card_ripple_background_light">#27ffffff</color>
+    <color name="car_card_ripple_background">@color/car_card_ripple_background_dark</color>
+
+    <color name="car_card_ripple_light_color_background_dark">#8F000000</color>
+    <color name="car_card_ripple_light_color_background_light">#8F000000</color>
+    <color name="car_card_ripple_light_color_background">@color/car_card_ripple_light_color_background_dark</color>
+
+    <color name="car_controller_ripple_dark">#b27da9c7</color>
+    <color name="car_controller_ripple_light">#66ffffff</color>
+
+    <color name="car_overscroll_glow_light">@color/car_grey_400</color>
+    <color name="car_overscroll_glow_dark">@color/car_grey_900</color>
+    <color name="car_overscroll_glow">@color/car_overscroll_glow_light</color>
+
+    <color name="car_tint_light">@color/car_grey_50</color>
+    <color name="car_tint_dark">@color/car_grey_900</color>
+    <color name="car_tint">@color/car_tint_dark</color>
+
+    <color name="car_list_divider">#1f000000</color>
+    <color name="car_list_divider_light">#1fffffff</color>
+    <color name="car_list_divider_dark">#1f000000</color>
+
+    <color name="car_ripple">#20444444</color>
+
+    <color name="car_scrollbar_thumb">#80bdbdbd</color>
+
+    <color name="car_focused_color">@color/car_teal_200</color>
+
+    <color name="car_music_accent_color">#ff9700</color>
+
+    <color name="car_error_screen">#ff1e272e</color>
+
+    <color name="car_unavailable_category">@color/car_blue_grey_800</color>
+</resources>
diff --git a/car-stream-ui-lib/res/values/dimens.xml b/car-stream-ui-lib/res/values/dimens.xml
index f350db7..e5aa37e 100644
--- a/car-stream-ui-lib/res/values/dimens.xml
+++ b/car-stream-ui-lib/res/values/dimens.xml
@@ -14,6 +14,75 @@
 limitations under the License.
 -->
 <resources>
+    <dimen name="car_headline0_size">50sp</dimen>
+    <dimen name="car_headline1_size">45sp</dimen>
+    <dimen name="car_headline2_size">36sp</dimen>
+    <dimen name="car_title_size">26sp</dimen>
+    <dimen name="car_body1_size">32sp</dimen>
+    <dimen name="car_body2_size">26sp</dimen>
+    <dimen name="car_caption_size">18sp</dimen>
+    <dimen name="car_key1_size">40sp</dimen>
+    <dimen name="car_key2_size">18sp</dimen>
+    <dimen name="car_micro_size">20sp</dimen>
+
+    <dimen name="car_drawer_header_height">96dp</dimen>
+    <dimen name="car_drawer_header_menu_button_size">96dp</dimen>
+    <dimen name="car_drawer_standard_width">704dp</dimen>
+    <dimen name="car_drawer_max_width">884dp</dimen>
+    <dimen name="car_drawer_item_max_width">788dp</dimen>
+
+    <dimen name="car_list_item_height">88dp</dimen>
+    <dimen name="car_list_item_height_small">64dp</dimen>
+    <!-- Sample row height used for scroll bar calculations in the off chance that a view hasn't
+         been measured. It's highly unlikely that this value will actually be used for more than
+         a frame max. The sample row is a 96dp card + 16dp margin on either side. -->
+    <dimen name="car_sample_row_height">128dp</dimen>
+    <!-- The amount of space the LayoutManager will make sure the last item on the screen is
+         peeking before scrolling down -->
+    <dimen name="car_last_card_peek_amount">16dp</dimen>
+    <dimen name="car_paged_list_view_pagination_width">48dp</dimen>
+    <dimen name="car_paged_list_view_button_background_inset">24dp</dimen>
+    <dimen name="car_paged_list_view_scrollbar_thumb_margin">8dp</dimen>
+
+    <dimen name="scroll_button_size">48dp</dimen>
+    <dimen name="scroll_bar_thumb_width">12dp</dimen>
+
+    <!-- height of list dividers -->
+    <dimen name="car_divider_height">1dp</dimen>
+
+    <dimen name="car_list_item_icon_size">64dp</dimen>
+    <dimen name="car_list_item_icon_right_margin">32dp</dimen>
+    <dimen name="car_list_item_icon_size_small">32dp</dimen>
+    <dimen name="car_list_item_small_icon_size">56dp</dimen>
+    <dimen name="car_list_item_right_icon_margin">20dp</dimen>
+    <dimen name="car_list_item_right_icon_size">56dp</dimen>
+    <dimen name="car_list_unavailable_category_item_height">108dp</dimen>
+    <dimen name="car_list_truncated_list_card_height">108dp</dimen>
+    <dimen name="car_list_truncated_list_card_elevation">8dp</dimen>
+    <dimen name="car_list_truncated_list_icon_size">56dp</dimen>
+    <dimen name="car_list_truncated_list_icon_padding">22dp</dimen>
+    <dimen name="car_list_truncated_list_drawable_padding">48dp</dimen>
+    <dimen name="car_list_truncated_list_padding">10dp</dimen>
+    <dimen name="car_text_vertical_margin">2dp</dimen>
+    <dimen name="car_card_bottom_margin">8dp</dimen>
+
+    <dimen name="car_touch_feedback_radius">32dp</dimen>
+    <dimen name="car_card_view_corner_radius">2dp</dimen>
+    <dimen name="car_card_view_elevation">8dp</dimen>
+    <dimen name="car_fab_focused_stroke_width">8dp</dimen>
+    <dimen name="car_fab_focused_growth">1.2dp</dimen>
+    <!-- The minimum the scrollbar thumb can shrink to -->
+    <dimen name="min_thumb_height">48dp</dimen>
+    <!-- The maximum the scrollbar thumb can grow to -->
+    <dimen name="max_thumb_height">128dp</dimen>
+
+    <dimen name="car_standard_width">800dp</dimen>
+    <dimen name="car_card_max_width">768dp</dimen>
+    <dimen name="car_card_margin">96dp</dimen>
+    <dimen name="car_drawer_margin_right">96dp</dimen>
+
+    <dimen name="car_drawer_button_container_width">@dimen/stream_content_keyline_2</dimen>
+
     <!-- The margin on both sizes of the screen. This margin limits the amount of space that
          content can take up on screen. -->
     <dimen name="stream_margin_size">16dp</dimen>
diff --git a/car-stream-ui-lib/res/values/styles.xml b/car-stream-ui-lib/res/values/styles.xml
new file mode 100644
index 0000000..e28a45a
--- /dev/null
+++ b/car-stream-ui-lib/res/values/styles.xml
@@ -0,0 +1,166 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android" >
+    <style name="CarHeadline0" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline0_size</item>
+        <item name="android:textColor">@color/car_headline0</item>
+    </style>
+
+    <style name="CarHeadline0.Dark" >
+        <item name="android:textColor">@color/car_headline0_dark</item>
+    </style>
+
+    <style name="CarHeadline0.Light" >
+        <item name="android:textColor">@color/car_headline0_light</item>
+    </style>
+
+    <style name="CarHeadline1" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline1_size</item>
+        <item name="android:textColor">@color/car_headline1</item>
+    </style>
+
+    <style name="CarHeadline1.Dark" >
+        <item name="android:textColor">@color/car_headline1_dark</item>
+    </style>
+
+    <style name="CarHeadline1.Light" >
+        <item name="android:textColor">@color/car_headline1_light</item>
+    </style>
+
+    <style name="CarHeadline2" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline2_size</item>
+        <item name="android:textColor">@color/car_headline1</item>
+
+    </style>
+
+    <style name="CarHeadline2.Dark" >
+        <item name="android:textColor">@color/car_headline1_dark</item>
+    </style>
+
+    <style name="CarHeadline2.Light" >
+        <item name="android:textColor">@color/car_headline1_light</item>
+    </style>
+
+    <style name="CarTitle" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_title_size</item>
+        <item name="android:textColor">@color/car_title</item>
+        <item name="android:includeFontPadding">false</item>
+    </style>
+
+    <style name="CarTitle.Dark" >
+        <item name="android:textColor">@color/car_title_dark</item>
+    </style>
+
+    <style name="CarTitle.Light" >
+        <item name="android:textColor">@color/car_title_light</item>
+    </style>
+
+    <style name="CarBody1" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body1_size</item>
+        <item name="android:textColor">@color/car_body1</item>
+    </style>
+
+    <style name="CarBody1.Dark" >
+        <item name="android:textColor">@color/car_body1_dark</item>
+    </style>
+
+    <style name="CarBody1.Light" >
+        <item name="android:textColor">@color/car_body1_light</item>
+    </style>
+
+    <style name="CarBody2" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body2_size</item>
+        <item name="android:textColor">@color/car_body2</item>
+        <item name="android:includeFontPadding">false</item>
+    </style>
+
+    <style name="CarBody2.Light" >
+        <item name="android:textColor">@color/car_body2_light</item>
+    </style>
+
+    <style name="CarKey1" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_key1_size</item>
+        <item name="android:textColor">@color/car_key1</item>
+    </style>
+
+    <style name="CarKey1.Dark" >
+        <item name="android:textColor">@color/car_key1_dark</item>
+    </style>
+
+    <style name="CarKey1.Light" >
+        <item name="android:textColor">@color/car_key1_light</item>
+    </style>
+
+    <style name="CarKey2" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_key2_size</item>
+        <item name="android:textColor">@color/car_key2</item>
+    </style>
+
+    <style name="CarKey2.Dark" >
+        <item name="android:textColor">@color/car_key2_dark</item>
+    </style>
+
+    <style name="CarKey2.Light" >
+        <item name="android:textColor">@color/car_key2_light</item>
+    </style>
+
+    <style name="CarCaption" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_caption_size</item>
+        <item name="android:textColor">@color/car_caption</item>
+    </style>
+
+    <style name="CarCaption.Dark" >
+        <item name="android:textColor">@color/car_caption_dark</item>
+    </style>
+
+    <style name="CarCaption.Light" >
+        <item name="android:textColor">@color/car_caption_light</item>
+    </style>
+
+    <style name="CarMicro" >
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_micro_size</item>
+        <item name="android:textColor">@color/car_micro</item>
+    </style>
+
+    <style name="CarMicro.Dark" >
+        <item name="android:textColor">@color/car_micro_dark</item>
+    </style>
+
+    <style name="CarMicro.Light" >
+        <item name="android:textColor">@color/car_micro_light</item>
+    </style>
+
+    <style name="CarDrawerArrowDrawable" >
+        <item name="carArrowColor">@android:color/white</item>
+        <item name="carArrowSpinBars">true</item>
+        <item name="carArrowThickness">2.5dp</item>
+        <item name="carArrowDrawableSize">@dimen/car_drawer_header_menu_button_size</item>
+        <item name="carArrowTopBottomBarSize">18dp</item>
+        <item name="carArrowBarSize">28dp</item>
+        <item name="carArrowMiddleBarSize">24dp</item>
+        <item name="carArrowGapBetweenBars">6dp</item>
+    </style>
+</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 1d90c17..2bef07c 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
@@ -20,7 +20,6 @@
 import android.os.Bundle;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
-import android.support.car.ui.PagedListView;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.app.AppCompatActivity;
@@ -33,6 +32,7 @@
 import android.widget.ProgressBar;
 
 import com.android.car.stream.ui.R;
+import com.android.car.view.PagedListView;
 
 import java.util.Stack;
 
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
index ad9fe1c..404d9e8 100644
--- a/car-stream-ui-lib/src/com/android/car/app/CarDrawerAdapter.java
+++ b/car-stream-ui-lib/src/com/android/car/app/CarDrawerAdapter.java
@@ -6,13 +6,13 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.StringRes;
-import android.support.car.ui.PagedListView;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.car.stream.ui.R;
+import com.android.car.view.PagedListView;
 
 /**
  * Base Adapter for displaying items in the CarDrawerActivity's Drawer which uses a PagedListView.
diff --git a/car-stream-ui-lib/src/com/android/car/view/CarItemAnimator.java b/car-stream-ui-lib/src/com/android/car/view/CarItemAnimator.java
new file mode 100644
index 0000000..73023f8
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/CarItemAnimator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+
+/**
+ * {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior.
+ * @hide
+ */
+public class CarItemAnimator extends DefaultItemAnimator {
+
+    private final CarLayoutManager mLayoutManager;
+
+    public CarItemAnimator(CarLayoutManager layoutManager) {
+        mLayoutManager = layoutManager;
+    }
+
+    @Override
+    public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+            RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
+        // The default behavior will cross fade the old view and the new one. However, if we
+        // have a card on a colored background, it will make it appear as if a changing card
+        // fades in and out.
+        float alpha = 0f;
+        if (newHolder != null) {
+            alpha = newHolder.itemView.getAlpha();
+        }
+        boolean ret = super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
+        if (newHolder != null) {
+            newHolder.itemView.setAlpha(alpha);
+        }
+        return ret;
+    }
+
+    @Override
+    public void onMoveFinished(RecyclerView.ViewHolder item) {
+        // The item animator uses translation heavily internally. However, we also use translation
+        // to create the paging affect. When an item's move is animated, it will mess up the
+        // translation we have set on it so we must re-offset the rows once the animations finish.
+
+        // isRunning(ItemAnimationFinishedListener) is the awkward API used to determine when all
+        // animations have finished.
+        isRunning(mFinishedListener);
+    }
+
+    private final ItemAnimatorFinishedListener mFinishedListener =
+            new ItemAnimatorFinishedListener() {
+        @Override
+        public void onAnimationsFinished() {
+            mLayoutManager.offsetRows();
+        }
+    };
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/CarLayoutManager.java b/car-stream-ui-lib/src/com/android/car/view/CarLayoutManager.java
new file mode 100644
index 0000000..1ddc8eb
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/CarLayoutManager.java
@@ -0,0 +1,1529 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.car.stream.ui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
+ * it has a few tricks up its sleeve.
+ * <ol>
+ *    <li>In a normal ListView, when views reach the top of the list, they are clipped. In
+ *        CarLayoutManager, views have the option of flying off of the top of the screen as the
+ *        next row settles in to place. This functionality can be enabled or disabled with
+ *        {@link #setOffsetRows(boolean)}.
+ *    <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle
+ *        on the next page. {@link #FLING_THRESHOLD_TO_PAGINATE} and
+ *        {@link #DRAG_DISTANCE_TO_PAGINATE} can be set to have the list settle on the next item
+ *        instead of the next page for small gestures.
+ *    <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that
+ *        the last page can be properly aligned.
+ * </ol>
+ *
+ * This LayoutManger should be used with {@link CarRecyclerView}.
+ */
+public class CarLayoutManager extends RecyclerView.LayoutManager {
+    private static final String TAG = "CarLayoutManager";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Any fling below the threshold will just scroll to the top fully visible row. The units is
+     * whatever {@link android.widget.Scroller} would return.
+     *
+     * A reasonable value is ~200
+     *
+     * This can be disabled by setting the threshold to -1.
+     */
+    private static final int FLING_THRESHOLD_TO_PAGINATE = -1;
+
+    /**
+     * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row.
+     *
+     * A reasonable value is 15.
+     *
+     * This can be disabled by setting the distance to -1.
+     */
+    private static final int DRAG_DISTANCE_TO_PAGINATE = -1;
+
+    /**
+     * If you scroll really quickly, you can hit the end of the laid out rows before Android has a
+     * chance to layout more. To help counter this, we can layout a number of extra rows past
+     * wherever the focus is if necessary.
+     */
+    private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2;
+
+    /**
+     * Scroll bar calculation is a bit complicated. This basically defines the granularity we want
+     * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement.
+     * Setting it too big will risk an overflow (although there is no performance impact). Ideally
+     * we want to set this higher than the height of our list view. We can't use our list view
+     * height directly though because we might run into situations where getHeight() returns 0, for
+     * example, when the view is not yet measured.
+     */
+    private static final int SCROLL_RANGE = 1000;
+
+    @ScrollStyle private final int SCROLL_TYPE = MARIO;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MARIO, SUPER_MARIO})
+    private @interface ScrollStyle {}
+    private static final int MARIO = 0;
+    private static final int SUPER_MARIO = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BEFORE, AFTER})
+    private @interface LayoutDirection {}
+    private static final int BEFORE = 0;
+    private static final int AFTER = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE})
+    public @interface RowOffsetMode {}
+    public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0;
+    public static final int ROW_OFFSET_MODE_PAGE = 1;
+
+    public interface OnItemsChangedListener {
+        void onItemsChanged();
+    }
+
+    private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2);
+    private final Context mContext;
+
+    /** Determines whether or not rows will be offset as they slide off screen **/
+    private boolean mOffsetRows = false;
+    /** Determines whether rows will be offset individually or a page at a time **/
+    @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE;
+
+    /**
+     * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the
+     * scroll state to be used anywhere.
+     */
+    private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+    /**
+     * Used to inspect the current scroll state to help with the various calculations.
+     **/
+    private CarSmoothScroller mSmoothScroller;
+    private OnItemsChangedListener mItemsChangedListener;
+
+    /** The distance that the list has actually scrolled in the most recent drag gesture **/
+    private int mLastDragDistance = 0;
+    /** True if the current drag was limited/capped because it was at some boundary **/
+    private boolean mReachedLimitOfDrag;
+    /**
+     * The values are continuously updated to keep track of where the current page boundaries are
+     * on screen. The anchor page break is the page break that is currently within or at the
+     * top of the viewport. The Upper page break is the page break before it and the lower page
+     * break is the page break after it.
+     *
+     * A page break will be set to -1 if it is unknown or n/a.
+     * @see #updatePageBreakPositions()
+     */
+    private int mItemCountDuringLastPageBreakUpdate;
+    // The index of the first item on the current page
+    private int mAnchorPageBreakPosition = 0;
+    // The index of the first item on the previous page
+    private int mUpperPageBreakPosition = -1;
+    // The index of the first item on the next page
+    private int mLowerPageBreakPosition = -1;
+    /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. **/
+    private int mLastChildPositionToRequestFocus = -1;
+    private int mSampleViewHeight = -1;
+
+    /**
+     * Set the anchor to the following position on the next layout pass.
+     */
+    private int mPendingScrollPosition = -1;
+
+    public CarLayoutManager(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new RecyclerView.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        return true;
+    }
+
+    /**
+     * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should:
+     * <ol>
+     *    <li>Check the current views to get the current state of affairs
+     *    <li>Detach all views from the window (a lightweight operation) so that rows
+     *        not re-added will be removed after onLayoutChildren.
+     *    <li>Re-add rows as necessary.
+     * </ol>
+     *
+     * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        /**
+         * The anchor view is the first fully visible view on screen at the beginning
+         * of onLayoutChildren (or 0 if there is none). This row will be laid out first. After that,
+         * layoutNextRow will layout rows above and below it until the boundaries of what should
+         * be laid out have been reached. See {@link #shouldLayoutNextRow(View, int)} for
+         * more information.
+         */
+        int anchorPosition = 0;
+        int anchorTop = -1;
+        if (mPendingScrollPosition == -1) {
+            View anchor = getFirstFullyVisibleChild();
+            if (anchor != null) {
+                anchorPosition = getPosition(anchor);
+                anchorTop = getDecoratedTop(anchor);
+            }
+        } else {
+            anchorPosition = mPendingScrollPosition;
+            mPendingScrollPosition = -1;
+            mAnchorPageBreakPosition = anchorPosition;
+            mUpperPageBreakPosition = -1;
+            mLowerPageBreakPosition = -1;
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, String.format(
+                    ":: onLayoutChildren anchorPosition:%s, anchorTop:%s,"
+                            + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s,"
+                            + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s",
+                    anchorPosition, anchorTop, mPendingScrollPosition, mAnchorPageBreakPosition,
+                    mUpperPageBreakPosition, mLowerPageBreakPosition));
+        }
+
+        /**
+         * Detach all attached view for 2 reasons:
+         * <ol>
+         *     <li> So that views are put in the scrap heap. This enables us to call
+         *          {@link RecyclerView.Recycler#getViewForPosition(int)} which will either return
+         *          one of these detached views if it is in the scrap heap, one from the
+         *          recycled pool (will only call onBind in the adapter), or create an entirely new
+         *          row if needed (will call onCreate and onBind in the adapter).
+         *     <li> So that views are automatically removed if they are not manually re-added.
+         * </ol>
+         */
+        detachAndScrapAttachedViews(recycler);
+
+        // Layout new rows.
+        View anchor = layoutAnchor(recycler, anchorPosition, anchorTop);
+        if (anchor != null) {
+            View adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+            adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        }
+
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (DEBUG&& getChildCount() > 1) {
+            Log.v(TAG, "Currently showing " + getChildCount() + " views " +
+                    getPosition(getChildAt(0)) + " to " +
+                    getPosition(getChildAt(getChildCount() - 1)) + " anchor " + anchorPosition);
+        }
+    }
+
+    /**
+     * scrollVerticallyBy does the work of what should happen when the list scrolls in addition
+     * to handling cases where the list hits the end. It should be lighter weight than
+     * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list
+     * and removes views that have gone out of bounds and lays out new ones that scroll in.
+     *
+     * @param dy The amount that the list is supposed to scroll.
+     *               > 0 means the list is scrolling down.
+     *               < 0 means the list is scrolling up.
+     * @param recycler The recycler that enables views to be reused or created as they scroll in.
+     * @param state Various information about the current state of affairs.
+     * @return The amount the list actually scrolled.
+     *
+     * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public int scrollVerticallyBy(
+            int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+        // If the list is empty, we can prevent the overscroll glow from showing by just
+        // telling RecycerView that we scrolled.
+        if (getItemCount() == 0) {
+            return dy;
+        }
+
+        // Prevent redundant computations if there is definitely nowhere to scroll to.
+        if (getChildCount() <= 1 || dy == 0) {
+            return 0;
+        }
+
+        View firstChild = getChildAt(0);
+        if (firstChild == null) {
+            return 0;
+        }
+        int firstChildPosition = getPosition(firstChild);
+        RecyclerView.LayoutParams firstChildParams = getParams(firstChild);
+        int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin;
+
+        View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex());
+        if (lastFullyVisibleView == null) {
+            return 0;
+        }
+        boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1;
+
+        View firstFullyVisibleChild = getFirstFullyVisibleChild();
+        if (firstFullyVisibleChild == null) {
+            return 0;
+        }
+        int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild);
+        RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild);
+        int topRemainingSpace = getDecoratedTop(firstFullyVisibleChild)
+                - firstFullyVisibleChildParams.topMargin - getPaddingTop();
+
+        if (isLastViewVisible && firstFullyVisiblePosition == mAnchorPageBreakPosition
+                && dy > topRemainingSpace && dy > 0) {
+            // Prevent dragging down more than 1 page. As a side effect, this also prevents you
+            // from dragging past the bottom because if you are on the second to last page, it
+            // prevents you from dragging past the last page.
+            dy = topRemainingSpace;
+            mReachedLimitOfDrag = true;
+        } else if (dy < 0 && firstChildPosition == 0
+                && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) {
+            // Prevent scrolling past the beginning
+            dy = firstChildTopWithMargin - getPaddingTop();
+            mReachedLimitOfDrag = true;
+        } else {
+            mReachedLimitOfDrag = false;
+        }
+
+        boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING;
+        if (isDragging) {
+            mLastDragDistance += dy;
+        }
+        // We offset by -dy because the views translate in the opposite direction that the
+        // list scrolls (think about it.)
+        offsetChildrenVertical(-dy);
+
+        // The last item in the layout should never scroll above the viewport
+        View view = getChildAt(getChildCount() - 1);
+        if (view.getTop() < 0) {
+            view.setTop(0);
+        }
+
+        // This is the meat of this function. We remove views on the trailing edge of the scroll
+        // and add views at the leading edge as necessary.
+        View adjacentRow;
+        if (dy > 0) {
+            recycleChildrenFromStart(recycler);
+            adjacentRow = getChildAt(getChildCount() - 1);
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        } else {
+            recycleChildrenFromEnd(recycler);
+            adjacentRow = getChildAt(0);
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+        }
+        // Now that the correct views are laid out, offset rows as necessary so we can do whatever
+        // fancy animation we want such as having the top view fly off the screen as the next one
+        // settles in to place.
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (getChildCount() >  1) {
+            if (DEBUG) {
+                Log.v(TAG, String.format("Currently showing  %d views (%d to %d)",
+                        getChildCount(), getPosition(getChildAt(0)),
+                        getPosition(getChildAt(getChildCount() - 1))));
+            }
+        }
+
+        return dy;
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mPendingScrollPosition = position;
+        requestLayout();
+    }
+
+    @Override
+    public void smoothScrollToPosition(
+            RecyclerView recyclerView, RecyclerView.State state, int position) {
+        /**
+         * startSmoothScroll will handle stopping the old one if there is one.
+         * We only keep a copy of it to handle the translation of rows as they slide off the screen
+         * in {@link #offsetRowsWithPageBreak()}
+         */
+        mSmoothScroller = new CarSmoothScroller(mContext, position);
+        mSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(mSmoothScroller);
+    }
+
+    /**
+     * Miscellaneous bookkeeping.
+     */
+    @Override
+    public void onScrollStateChanged(int state) {
+        if (DEBUG) {
+            Log.v(TAG, ":: onScrollStateChanged " + state);
+        }
+        if (state == RecyclerView.SCROLL_STATE_IDLE) {
+            // If the focused view is off screen, give focus to one that is.
+            // If the first fully visible view is first in the list, focus the first item.
+            // Otherwise, focus the second so that you have the first item as scrolling context.
+            View focusedChild = getFocusedChild();
+            if (focusedChild != null
+                    && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom()
+                    || getDecoratedBottom(focusedChild) <= getPaddingTop())) {
+                focusedChild.clearFocus();
+                requestLayout();
+            }
+
+        } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
+            mLastDragDistance = 0;
+        }
+
+        if (state != RecyclerView.SCROLL_STATE_SETTLING) {
+            mSmoothScroller = null;
+        }
+
+        mScrollState = state;
+        updatePageBreakPositions();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        super.onItemsChanged(recyclerView);
+        if (mItemsChangedListener != null) {
+            mItemsChangedListener.onItemsChanged();
+        }
+        // When item changed, our sample view height is no longer accurate, and need to be
+        // recomputed.
+        mSampleViewHeight = -1;
+    }
+
+    /**
+     * Gives us the opportunity to override the order of the focused views.
+     * By default, it will just go from top to bottom. However, if there is no focused views, we
+     * take over the logic and start the focused views from the middle of what is visible and move
+     * from there until the end of the laid out views in the specified direction.
+     */
+    @Override
+    public boolean onAddFocusables(
+            RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            // If there is a view that already has focus, we can just return false and the normal
+            // Android addFocusables will work fine.
+            return false;
+        }
+
+        // Now we know that there isn't a focused view. We need to set up focusables such that
+        // instead of just focusing the first item that has been laid out, it focuses starting
+        // from a visible item.
+
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        if (firstFullyVisibleChildIndex == -1) {
+            // Somehow there is a focused view but there is no fully visible view. There shouldn't
+            // be a way for this to happen but we'd better stop here and return instead of
+            // continuing on with -1.
+            Log.w(TAG, "There is a focused child but no first fully visible child.");
+            return false;
+        }
+        View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex);
+        int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild);
+
+        int firstFocusableChildIndex = firstFullyVisibleChildIndex;
+        if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) {
+            // We are somewhere in the middle of the list. Instead of starting focus on the first
+            // item, start focus on the second item to give some context that we aren't at
+            // the beginning.
+            firstFocusableChildIndex++;
+        }
+
+        if (direction == View.FOCUS_FORWARD) {
+            // Iterate from the first focusable view to the end.
+            for (int i = firstFocusableChildIndex; i < getChildCount(); i++) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            // Iterate from the first focusable view to the beginning.
+            for (int i = firstFocusableChildIndex; i >= 0; i--) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
+                                    RecyclerView.State state) {
+        return null;
+    }
+
+    /**
+     * This is the function that decides where to scroll to when a new view is focused.
+     * You can get the position of the currently focused child through the child parameter.
+     * Once you have that, determine where to smooth scroll to and scroll there.
+     *
+     * @param parent The RecyclerView hosting this LayoutManager
+     * @param state Current state of RecyclerView
+     * @param child Direct child of the RecyclerView containing the newly focused view
+     * @param focused The newly focused view. This may be the same view as child or it may be null
+     * @return true if the default scroll behavior should be suppressed
+     */
+    @Override
+    public boolean onRequestChildFocus(RecyclerView parent, RecyclerView.State state,
+                                       View child, View focused) {
+        if (child == null) {
+            Log.w(TAG, "onRequestChildFocus with a null child!");
+            return true;
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child,
+                    focused));
+        }
+
+        // We have several distinct scrolling methods. Each implementation has been delegated
+        // to its own method.
+        if (SCROLL_TYPE == MARIO) {
+            return onRequestChildFocusMarioStyle(parent, child);
+        } else if (SCROLL_TYPE == SUPER_MARIO) {
+            return onRequestChildFocusSuperMarioStyle(parent, state, child);
+        } else {
+            throw new IllegalStateException("Unknown scroll type (" + SCROLL_TYPE + ")");
+        }
+    }
+
+    /**
+     * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar
+     * reaches the bottom of the screen when the last item is fully visible. This is because
+     * there are multiple points that could be considered the bottom since the last item can scroll
+     * past the bottom edge of the screen.
+     *
+     * To find the extent, we divide the number of items that can fit on screen by the number of
+     * items in total.
+     */
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.State state) {
+        if (getChildCount() <= 1) {
+            return 0;
+        }
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+
+        if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) {
+            return SCROLL_RANGE;
+        } else {
+            return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount();
+        }
+    }
+
+    /**
+     * The scrolling offset is calculated by determining what position is at the top of the list.
+     * However, instead of using fixed integer positions for each row, the scroll position is
+     * factored in and the position is recalculated as a float that takes in to account the
+     * current scroll state. This results in a smooth animation for the scrollbar when the user
+     * scrolls the list.
+     */
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.State state) {
+        View firstChild = getFirstFullyVisibleChild();
+        if (firstChild == null) {
+            return 0;
+        }
+
+        RecyclerView.LayoutParams params = getParams(firstChild);
+        int firstChildPosition = getPosition(firstChild);
+
+        // Assume the previous view is the same height as the current one.
+        float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin)
+                / (float) (getDecoratedMeasuredHeight(firstChild)
+                + params.topMargin + params.bottomMargin);
+        // If the previous view is actually larger than the current one then this the percent
+        // can be greater than 1.
+        percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1);
+
+        float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing;
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+        int positionWhenLastItemIsVisible =
+                state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen;
+
+        if (positionWhenLastItemIsVisible <= 0) {
+            return 0;
+        }
+
+        if (currentPosition >= positionWhenLastItemIsVisible) {
+            return SCROLL_RANGE;
+        }
+
+        return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible);
+    }
+
+    /**
+     * The range of the scrollbar can be understood as the granularity of how we want the
+     * scrollbar to scroll.
+     */
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.State state) {
+        return SCROLL_RANGE;
+    }
+
+    /**
+     * @return The first view that starts on screen. It assumes that it fully fits on the screen
+     *         though. If the first fully visible child is also taller than the screen then it will
+     *         still be returned. However, since the LayoutManager snaps to view starts, having
+     *         a row that tall would lead to a broken experience anyways.
+     */
+    public int getFirstFullyVisibleChildIndex() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public View getFirstFullyVisibleChild() {
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        View firstChild = null;
+        if (firstFullyVisibleChildIndex != -1) {
+            firstChild = getChildAt(firstFullyVisibleChildIndex);
+        }
+        return firstChild;
+    }
+
+    /**
+     * @return The last view that ends on screen. It assumes that the start is also on screen
+     *         though. If the last fully visible child is also taller than the screen then it will
+     *         still be returned. However, since the LayoutManager snaps to view starts, having
+     *         a row that tall would lead to a broken experience anyways.
+     */
+    public int getLastFullyVisibleChildIndex() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+            int listBottom = getHeight() - getPaddingBottom();
+            if (childBottom <= listBottom) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @return Whether or not the first view is fully visible.
+     */
+    public boolean isAtTop() {
+        // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views
+        // and also means that the list is at the top.
+        return getFirstFullyVisibleChildIndex() <= 0;
+    }
+
+    /**
+     * @return Whether or not the last view is fully visible.
+     */
+    public boolean isAtBottom() {
+        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+        if (lastFullyVisibleChildIndex == -1) {
+            return true;
+        }
+        View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex);
+        return getPosition(lastFullyVisibleChild) == getItemCount() - 1;
+    }
+
+    public void setOffsetRows(boolean offsetRows) {
+        mOffsetRows = offsetRows;
+        if (offsetRows) {
+            offsetRows();
+        } else {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).setTranslationY(0);
+            }
+        }
+    }
+
+    public void setRowOffsetMode(@RowOffsetMode int mode) {
+        if (mode == mRowOffsetMode) {
+            return;
+        }
+        mRowOffsetMode = mode;
+        offsetRows();
+    }
+
+    public void setItemsChangedListener(OnItemsChangedListener listener) {
+        mItemsChangedListener = listener;
+    }
+
+    /**
+     * Finish the pagination taking into account where the gesture started (not where we are now).
+     *
+     * @return Whether the list was scrolled as a result of the fling.
+     */
+    public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) {
+        if (getChildCount() == 0) {
+            return false;
+        }
+
+        if (mReachedLimitOfDrag) {
+            return false;
+        }
+
+        // If the fling was too slow or too short, settle on the first fully visible row instead.
+        if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE
+                || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) {
+            int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+            if (firstFullyVisibleChildIndex != -1) {
+                int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex));
+                parent.smoothScrollToPosition(scrollPosition);
+                return true;
+            }
+            return false;
+        }
+
+        // Finish the pagination taking into account where the gesture
+        // started (not where we are now).
+        boolean isDownGesture = flingVelocity > 0
+                || (flingVelocity == 0 && mLastDragDistance >= 0);
+        boolean isUpGesture = flingVelocity < 0
+                || (flingVelocity == 0 && mLastDragDistance < 0);
+        if (isDownGesture && mLowerPageBreakPosition != -1) {
+            // If the last view is fully visible then only settle on the first fully visible view
+            // instead of the original page down position. However, don't page down if the last
+            // item has come fully into view.
+            parent.smoothScrollToPosition(mAnchorPageBreakPosition);
+            return true;
+        } else if (isUpGesture && mUpperPageBreakPosition != -1) {
+            parent.smoothScrollToPosition(mUpperPageBreakPosition);
+            return true;
+        } else {
+            Log.e(TAG, "Error setting scroll for fling! flingVelocity: \t" + flingVelocity +
+                    "\tlastDragDistance: " + mLastDragDistance + "\tpageUpAtStartOfDrag: " +
+                    mUpperPageBreakPosition + "\tpageDownAtStartOfDrag: " +
+                    mLowerPageBreakPosition);
+            // As a last resort, at the last smooth scroller target position if there is one.
+            if (mSmoothScroller != null) {
+                parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return The position that paging up from the current position would settle at.
+     */
+    public int getPageUpPosition() {
+        return mUpperPageBreakPosition;
+    }
+
+    /**
+     * @return The position that paging down from the current position would settle at.
+     */
+    public int getPageDownPosition() {
+        return mLowerPageBreakPosition;
+    }
+
+    /**
+     * Layout the anchor row. The anchor row is the first fully visible row.
+     *
+     * @param anchorTop The decorated top of the anchor. If it is not known or should be reset
+     *                  to the top, pass -1.
+     */
+    private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) {
+        if (anchorPosition > getItemCount() - 1) {
+            return null;
+        }
+        View anchor = recycler.getViewForPosition(anchorPosition);
+        RecyclerView.LayoutParams params = getParams(anchor);
+        measureChildWithMargins(anchor, 0, 0);
+        int left = getPaddingLeft() + params.leftMargin;
+        int top = (anchorTop == -1) ? params.topMargin : anchorTop;
+        int right = left + getDecoratedMeasuredWidth(anchor);
+        int bottom = top + getDecoratedMeasuredHeight(anchor);
+        layoutDecorated(anchor, left, top, right, bottom);
+        addView(anchor);
+        return anchor;
+    }
+
+    /**
+     * Lays out the next row in the specified direction next to the specified adjacent row.
+     *
+     * @param recycler The recycler from which a new view can be created.
+     * @param adjacentRow The View of the adjacent row which will be used to position the new one.
+     * @param layoutDirection The side of the adjacent row that the new row will be laid out on.
+     *
+     * @return The new row that was laid out.
+     */
+    private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow,
+                               @LayoutDirection int layoutDirection) {
+
+        int adjacentRowPosition = getPosition(adjacentRow);
+        int newRowPosition = adjacentRowPosition;
+        if (layoutDirection == BEFORE) {
+            newRowPosition = adjacentRowPosition - 1;
+        } else if (layoutDirection == AFTER) {
+            newRowPosition = adjacentRowPosition + 1;
+        }
+
+        // Because we detach all rows in onLayoutChildren, this will often just return a view from
+        // the scrap heap.
+        View newRow = recycler.getViewForPosition(newRowPosition);
+
+        measureChildWithMargins(newRow, 0, 0);
+        RecyclerView.LayoutParams newRowParams =
+                (RecyclerView.LayoutParams) newRow.getLayoutParams();
+        RecyclerView.LayoutParams adjacentRowParams =
+                (RecyclerView.LayoutParams) adjacentRow.getLayoutParams();
+        int left = getPaddingLeft() + newRowParams.leftMargin;
+        int right = left + getDecoratedMeasuredWidth(newRow);
+        int top, bottom;
+        if (layoutDirection == BEFORE) {
+            bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin;
+            top = bottom - getDecoratedMeasuredHeight(newRow);
+        } else {
+            top = getDecoratedBottom(adjacentRow) +
+                    adjacentRowParams.bottomMargin + newRowParams.topMargin;
+            bottom = top + getDecoratedMeasuredHeight(newRow);
+        }
+        layoutDecorated(newRow, left, top, right, bottom);
+
+        if (layoutDirection == BEFORE) {
+            addView(newRow, 0);
+        } else {
+            addView(newRow);
+        }
+
+        return newRow;
+    }
+
+    /**
+     * @return Whether another row should be laid out in the specified direction.
+     */
+    private boolean shouldLayoutNextRow(RecyclerView.State state, View adjacentRow,
+                                        @LayoutDirection int layoutDirection) {
+        int adjacentRowPosition = getPosition(adjacentRow);
+
+        if (layoutDirection == BEFORE) {
+            if (adjacentRowPosition == 0) {
+                // We already laid out the first row.
+                return false;
+            }
+        } else if (layoutDirection == AFTER) {
+            if (adjacentRowPosition >= state.getItemCount() - 1) {
+                // We already laid out the last row.
+                return false;
+            }
+        }
+
+        // If we are scrolling layout views until the target position.
+        if (mSmoothScroller != null) {
+            if (layoutDirection == BEFORE
+                    && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) {
+                return true;
+            } else if (layoutDirection == AFTER
+                    && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) {
+                return true;
+            }
+        }
+
+        View focusedRow = getFocusedChild();
+        if (focusedRow != null) {
+            int focusedRowPosition = getPosition(focusedRow);
+            if (layoutDirection == BEFORE && adjacentRowPosition
+                    >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            } else if (layoutDirection == AFTER && adjacentRowPosition
+                    <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            }
+        }
+
+        RecyclerView.LayoutParams params = getParams(adjacentRow);
+        int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin;
+        int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin;
+        if (layoutDirection == BEFORE
+                && adjacentRowTop < getPaddingTop() - getHeight()) {
+            // View is more than 1 page past the top of the screen and also past where the user has
+            // scrolled to. We want to keep one page past the top to make the scroll up calculation
+            // easier and scrolling smoother.
+            return false;
+        } else if (layoutDirection == AFTER
+                && adjacentRowBottom > getHeight() - getPaddingBottom()) {
+            // View is off of the bottom and also past where the user has scrolled to.
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Remove and recycle views that are no longer needed.
+     */
+    private void recycleChildrenFromStart(RecyclerView.Recycler recycler) {
+        // Start laying out children one page before the top of the viewport.
+        int childrenStart = getPaddingTop() - getHeight();
+
+        int focusedChildPosition = Integer.MAX_VALUE;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            int childEnd = getDecoratedBottom(child);
+            int childPosition = getPosition(child);
+
+            if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) {
+                break;
+            }
+
+            detachedCount++;
+        }
+
+        // Remove the number of views counted above. Done by removing the first child n times.
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(0);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /**
+     * Remove and recycle views that are no longer needed.
+     */
+    private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) {
+        // Layout views until the end of the viewport.
+        int childrenEnd = getHeight();
+
+        int focusedChildPosition = Integer.MIN_VALUE + 1;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int firstDetachedPos = 0;
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            int childStart = getDecoratedTop(child);
+            int childPosition = getPosition(child);
+
+            if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) {
+                break;
+            }
+
+            firstDetachedPos = i;
+            detachedCount++;
+        }
+
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(firstDetachedPos);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /**
+     * Offset rows to do fancy animations. If {@link #mOffsetRows} is false, this will do nothing.
+     *
+     * @see #offsetRowsIndividually()
+     * @see #offsetRowsByPage()
+     */
+    public void offsetRows() {
+        if (!mOffsetRows) {
+            return;
+        }
+
+        if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) {
+            offsetRowsByPage();
+        } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) {
+            offsetRowsIndividually();
+        }
+    }
+
+    /**
+     * Offset the single row that is scrolling off the screen such that by the time the next row
+     * reaches the top, it will have accelerated completely off of the screen.
+     */
+    private void offsetRowsIndividually() {
+        if (getChildCount() == 0) {
+            if (DEBUG) {
+                Log.d(TAG, ":: offsetRowsIndividually getChildCount=0");
+            }
+            return;
+        }
+
+        // Identify the dangling row. It will be the first row that is at the top of the
+        // list or above.
+        int danglingChildIndex = -1;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) {
+                danglingChildIndex = i;
+                break;
+            }
+        }
+
+        mAnchorPageBreakPosition = danglingChildIndex;
+
+        if (DEBUG) {
+            Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex);
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+        int maxDanglingViewTranslation;
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+
+            maxDanglingViewTranslation = listTopInWindow;
+            // If the child has a negative margin, we'll actually need to translate the view a
+            // little but further to get it completely off screen.
+            if (params.topMargin < 0) {
+                maxDanglingViewTranslation -= params.topMargin;
+            }
+            if (params.bottomMargin < 0) {
+                maxDanglingViewTranslation -= params.bottomMargin;
+            }
+
+            if (i < danglingChildIndex) {
+                child.setAlpha(0f);
+            } else if (i > danglingChildIndex) {
+                child.setAlpha(1f);
+                child.setTranslationY(0);
+            } else {
+                int totalScrollDistance = getDecoratedMeasuredHeight(child) +
+                        params.topMargin + params.bottomMargin;
+
+                int distanceLeftInScroll = getDecoratedBottom(child) +
+                        params.bottomMargin - getPaddingTop();
+                float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance;
+                float interpolatedPercentage =
+                        mDanglingRowInterpolator.getInterpolation(percentageIntoScroll);
+
+                child.setAlpha(1f);
+                child.setTranslationY(-(maxDanglingViewTranslation * interpolatedPercentage));
+            }
+        }
+    }
+
+    /**
+     * When the list scrolls, the entire page of rows will offset in one contiguous block. This
+     * significantly reduces the amount of extra motion at the top of the screen.
+     */
+    private void offsetRowsByPage() {
+        View anchorView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorView == null) {
+            if (DEBUG) {
+                Log.d(TAG, ":: offsetRowsByPage anchorView null");
+            }
+            return;
+        }
+        int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin;
+
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperViewTop = getDecoratedTop(upperPageBreakView)
+                - getParams(upperPageBreakView).topMargin;
+
+        int scrollDistance = upperViewTop - anchorViewTop;
+
+        int distanceLeft = anchorViewTop - getPaddingTop();
+        float scrollPercentage = (Math.abs(scrollDistance) - distanceLeft)
+                / (float) Math.abs(scrollDistance);
+
+        if (DEBUG) {
+            Log.d(TAG, String.format(
+                    ":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, scrollPercentage:%s",
+                    scrollDistance, distanceLeft, scrollPercentage));
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            int position = getPosition(child);
+            if (position < mUpperPageBreakPosition) {
+                child.setAlpha(0f);
+                child.setTranslationY(-listTopInWindow);
+            } else if (position < mAnchorPageBreakPosition) {
+                // If the child has a negative margin, we need to offset the row by a little bit
+                // extra so that it moves completely off screen.
+                RecyclerView.LayoutParams params = getParams(child);
+                int extraTranslation = 0;
+                if (params.topMargin < 0) {
+                    extraTranslation -= params.topMargin;
+                }
+                if (params.bottomMargin < 0) {
+                    extraTranslation -= params.bottomMargin;
+                }
+                int translation = (int) ((listTopInWindow + extraTranslation)
+                        * mDanglingRowInterpolator.getInterpolation(scrollPercentage));
+                child.setAlpha(1f);
+                child.setTranslationY(-translation);
+            } else {
+                child.setAlpha(1f);
+                child.setTranslationY(0);
+            }
+        }
+    }
+
+    /**
+     * Update the page break positions based on the position of the views on screen. This should
+     * be called whenever view move or change such as during a scroll or layout.
+     */
+    private void updatePageBreakPositions() {
+        if (getChildCount() == 0) {
+            if (DEBUG) {
+                Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0");
+            }
+            return;
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions " +
+                            "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+                            + "mLowerPageBreakPosition:%s",
+                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
+        }
+
+        // If the item count has changed, our page boundaries may no longer be accurate. This will
+        // force the page boundaries to reset around the current view that is closest to the top.
+        if (getItemCount() != mItemCountDuringLastPageBreakUpdate) {
+            if (DEBUG) {
+                Log.d(TAG, "Item count changed. Resetting page break positions.");
+            }
+            mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild());
+        }
+        mItemCountDuringLastPageBreakUpdate = getItemCount();
+
+        if (mAnchorPageBreakPosition == -1) {
+            Log.w(TAG, "Unable to update anchor positions. There is no anchor position.");
+            return;
+        }
+
+        View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorPageBreakView == null) {
+            return;
+        }
+        int topMargin = getParams(anchorPageBreakView).topMargin;
+        int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin;
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperPageBreakTop = upperPageBreakView == null ? Integer.MIN_VALUE :
+                getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+        if (DEBUG) {
+            Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s"
+                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+                            + "mLowerPageBreakPosition:%s", topMargin, anchorTop,
+                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
+        }
+
+        if (anchorTop < getPaddingTop()) {
+            // The anchor has moved above the viewport. We are now on the next page. Shift the page
+            // break positions and calculate a new lower one.
+            mUpperPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mLowerPageBreakPosition;
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) {
+            // The anchor has moved below the viewport. We are now on the previous page. Shift
+            // the page break positions and calculate a new upper one.
+            mLowerPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mUpperPageBreakPosition;
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+        } else {
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions " +
+                            "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+                            + "mLowerPageBreakPosition:%s",
+                    mAnchorPageBreakPosition, mUpperPageBreakPosition, mLowerPageBreakPosition));
+        }
+    }
+
+    /**
+     * @return The page break position of the page before the anchor page break position. However,
+     *         if it reaches the end of the laid out children or position 0, it will just return
+     *         that.
+     */
+    private int calculatePreviousPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+        View referenceView = findViewByPosition(position);
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int previousPagePosition = position;
+        while (previousPagePosition > 0) {
+            previousPagePosition--;
+            View child = findViewByPosition(previousPagePosition);
+            if (child == null) {
+                // View has not been laid out yet.
+                return previousPagePosition + 1;
+            }
+
+            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+
+            if (childTop < referenceViewTop - getHeight()) {
+                return previousPagePosition + 1;
+            }
+        }
+        // Beginning of the list.
+        return 0;
+    }
+
+    /**
+     * @return The page break position of the next page after the anchor page break position.
+     *         However, if it reaches the end of the laid out children or end of the list, it will
+     *         just return that.
+     */
+    private int calculateNextPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+
+        View referenceView = findViewByPosition(position);
+        if (referenceView == null) {
+            return position;
+        }
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int nextPagePosition = position;
+
+        // Search for the first child item after the referenceView that didn't fully fit on to the
+        // screen. The next page should start from the item before this child, so that users have
+        // a visual anchoring point of the page change.
+        while (position < getItemCount() - 1) {
+            nextPagePosition++;
+            View child = findViewByPosition(nextPagePosition);
+            if (child == null) {
+                // The next view has not been laid out yet.
+                return nextPagePosition - 1;
+            }
+
+            int childBottom = getDecoratedBottom(child) + getParams(child).bottomMargin;
+            if (childBottom - referenceViewTop > getHeight() - getPaddingTop()) {
+                // If choosing the previous child causes the view to snap back to the referenceView
+                // position, then skip that and go directly to the child. This avoids the case
+                // where a tall card in the layout causes the view to constantly snap back to
+                // the top when scrolled.
+                return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1;
+            }
+        }
+        // End of the list.
+        return nextPagePosition;
+    }
+
+    /**
+     * In this style, the focus will scroll down to the middle of the screen and lock there
+     * so that moving in either direction will move the entire list by 1.
+     */
+    private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) {
+        int focusedPosition = getPosition(child);
+        if (focusedPosition == mLastChildPositionToRequestFocus) {
+            return true;
+        }
+        mLastChildPositionToRequestFocus = focusedPosition;
+
+        int availableHeight = getAvailableHeight();
+        int focusedChildTop = getDecoratedTop(child);
+        int focusedChildBottom = getDecoratedBottom(child);
+
+        int childIndex = parent.indexOfChild(child);
+        // Iterate through children starting at the focused child to find the child above it to
+        // smooth scroll to such that the focused child will be as close to the middle of the screen
+        // as possible.
+        for (int i = childIndex; i >= 0; i--) {
+            View childAtI = getChildAt(i);
+            if (childAtI == null) {
+                Log.e(TAG, "Child is null at index " + i);
+                continue;
+            }
+            // We haven't found a view that is more than half of the recycler view height above it
+            // but we've reached the top so we can't go any further.
+            if (i == 0) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+
+            // Because we want to scroll to the first view that is less than half of the screen
+            // away from the focused view, we "look ahead" one view. When the look ahead view
+            // is more than availableHeight / 2 away, the current child at i is the one we want to
+            // scroll to. However, sometimes, that view can be null (ie, if the view is in
+            // transition). In that case, just skip that view.
+
+            View childBefore = getChildAt(i - 1);
+            if (childBefore == null) {
+                continue;
+            }
+            int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore);
+            int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore);
+
+            if (distanceToChildBeforeFromTop > availableHeight / 2
+                    || distanceToChildBeforeFromBottom > availableHeight) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * In this style, you can free scroll in the middle of the list but if you get to the edge,
+     * the list will advance to ensure that there is context ahead of the focused item.
+     */
+    private boolean onRequestChildFocusSuperMarioStyle(RecyclerView parent,
+                                                       RecyclerView.State state, View child) {
+        int focusedPosition = getPosition(child);
+        if (focusedPosition == mLastChildPositionToRequestFocus) {
+            return true;
+        }
+        mLastChildPositionToRequestFocus = focusedPosition;
+
+        int bottomEdgeThatMustBeOnScreen;
+        int focusedIndex = parent.indexOfChild(child);
+        // The amount of the last card at the end that must be showing to count as visible.
+        int peekAmount = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.car_last_card_peek_amount);
+        if (focusedPosition == state.getItemCount() - 1) {
+            // The last item is focused.
+            bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child);
+        } else if (focusedIndex == getChildCount() - 1) {
+            // The last laid out item is focused. Scroll enough so that the next card has at least
+            // the peek size visible
+            ViewGroup.MarginLayoutParams params =
+                    (ViewGroup.MarginLayoutParams) child.getLayoutParams();
+            // We add params.topMargin as an estimate because we don't actually know the top margin
+            // of the next row.
+            bottomEdgeThatMustBeOnScreen = getDecoratedBottom(child) +
+                    params.bottomMargin + params.topMargin + peekAmount;
+        } else {
+            View nextChild = getChildAt(focusedIndex + 1);
+            bottomEdgeThatMustBeOnScreen = getDecoratedTop(nextChild) + peekAmount;
+        }
+
+        if (bottomEdgeThatMustBeOnScreen > getHeight()) {
+            // We're going to have to scroll because the bottom edge that must be on screen is past
+            // the bottom.
+            int topEdgeToFindViewUnder = getPaddingTop() +
+                    bottomEdgeThatMustBeOnScreen - getHeight();
+
+            View nextChild = null;
+            for (int i = 0; i < getChildCount(); i++) {
+                View potentialNextChild = getChildAt(i);
+                RecyclerView.LayoutParams params = getParams(potentialNextChild);
+                float top = getDecoratedTop(potentialNextChild) - params.topMargin;
+                if (top >= topEdgeToFindViewUnder) {
+                    nextChild = potentialNextChild;
+                    break;
+                }
+            }
+
+            if (nextChild == null) {
+                Log.e(TAG, "There is no view under " + topEdgeToFindViewUnder);
+                return true;
+            }
+            int nextChildPosition = getPosition(nextChild);
+            parent.smoothScrollToPosition(nextChildPosition);
+        } else {
+            int firstFullyVisibleIndex = getFirstFullyVisibleChildIndex();
+            if (focusedIndex <= firstFullyVisibleIndex) {
+                parent.smoothScrollToPosition(Math.max(focusedPosition - 1, 0));
+            }
+        }
+        return true;
+    }
+
+    /**
+     * We don't actually know the size of every single view, only what is currently laid out.
+     * This makes it difficult to do accurate scrollbar calculations. However, lists in the car
+     * often consist of views with identical heights. Because of that, we can use
+     * a single sample view to do our calculations for. The main exceptions are in the first items
+     * of a list (hero card, last call card, etc) so if the first view is at position 0, we pick
+     * the next one.
+     *
+     * @return The decorated measured height of the sample view plus its margins.
+     */
+    private int getSampleViewHeight() {
+        if (mSampleViewHeight != -1) {
+            return mSampleViewHeight;
+        }
+        int sampleViewIndex = getFirstFullyVisibleChildIndex();
+        View sampleView = getChildAt(sampleViewIndex);
+        if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) {
+            sampleView = getChildAt(++sampleViewIndex);
+        }
+        RecyclerView.LayoutParams params = getParams(sampleView);
+        int height =
+                getDecoratedMeasuredHeight(sampleView) + params.topMargin + params.bottomMargin;
+        if (height == 0) {
+            // This can happen if the view isn't measured yet.
+            Log.w(TAG, "The sample view has a height of 0. Returning a dummy value for now " +
+                    "that won't be cached.");
+            height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height);
+        } else {
+            mSampleViewHeight = height;
+        }
+        return height;
+    }
+
+    /**
+     * @return The height of the RecyclerView excluding padding.
+     */
+    private int getAvailableHeight() {
+        return getHeight() - getPaddingTop() - getPaddingBottom();
+    }
+
+    /**
+     * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child
+     *         of {@link RecyclerView}.
+     */
+    private static RecyclerView.LayoutParams getParams(View view) {
+        return (RecyclerView.LayoutParams) view.getLayoutParams();
+    }
+
+    /**
+     * Custom {@link LinearSmoothScroller} that has:
+     *     a) Custom control over the speed of scrolls.
+     *     b) Scrolling snaps to start. All of our scrolling logic depends on that.
+     *     c) Keeps track of some state of the current scroll so that can aid in things like
+     *        the scrollbar calculations.
+     */
+    private final class CarSmoothScroller extends LinearSmoothScroller {
+        /** This value (150) was hand tuned by UX for what felt right. **/
+        private static final float MILLISECONDS_PER_INCH = 150f;
+        /** This value (0.45) was hand tuned by UX for what felt right. **/
+        private static final float DECELERATION_TIME_DIVISOR = 0.45f;
+        private static final int NON_TOUCH_MAX_DECELERATION_MS = 1000;
+
+        /** This value (1.8) was hand tuned by UX for what felt right. **/
+        private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
+
+        private final boolean mHasTouch;
+        private final int mTargetPosition;
+
+
+        public CarSmoothScroller(Context context, int targetPosition) {
+            super(context);
+            mTargetPosition = targetPosition;
+            mHasTouch = mContext.getResources().getBoolean(R.bool.car_true_for_touch);
+        }
+
+        @Override
+        public PointF computeScrollVectorForPosition(int i) {
+            if (getChildCount() == 0) {
+                return null;
+            }
+            final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex()));
+            final int direction = (mTargetPosition < firstChildPos) ? -1 : 1;
+            return new PointF(0, direction);
+        }
+
+        @Override
+        protected int getVerticalSnapPreference() {
+            // This is key for most of the scrolling logic that guarantees that scrolling
+            // will settle with a view aligned to the top.
+            return LinearSmoothScroller.SNAP_TO_START;
+        }
+
+        @Override
+        protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+            int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+            if (dy == 0) {
+                if (DEBUG) {
+                    Log.d(TAG, "Scroll distance is 0");
+                }
+                return;
+            }
+
+            final int time = calculateTimeForDeceleration(dy);
+            if (time > 0) {
+                action.update(0, -dy, time, mInterpolator);
+            }
+        }
+
+        @Override
+        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+        }
+
+        @Override
+        protected int calculateTimeForDeceleration(int dx) {
+            int time = (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR);
+            return mHasTouch ? time : Math.min(time, NON_TOUCH_MAX_DECELERATION_MS);
+        }
+
+        public int getTargetPosition() {
+            return mTargetPosition;
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/CarListItemViewHolder.java b/car-stream-ui-lib/src/com/android/car/view/CarListItemViewHolder.java
new file mode 100644
index 0000000..82dd834
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/CarListItemViewHolder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.car.stream.ui.R;
+
+/**
+ * ViewHolder for @layout/sdk_car_list_item that is used to handle the various sdk item templates
+ * @hide
+ */
+public class CarListItemViewHolder extends RecyclerView.ViewHolder {
+    public final FrameLayout iconContainer;
+    public final ImageView icon;
+    public final TextView title;
+    public final TextView text;
+    public final ImageView rightImage;
+    public final CheckBox rightCheckbox;
+    public final TextView rightText;
+    public final FrameLayout remoteViewsContainer;
+
+    public CarListItemViewHolder(View v, int viewStubLayoutId) {
+        super(v);
+        icon = (ImageView) v.findViewById(R.id.icon);
+        iconContainer = (FrameLayout) v.findViewById(R.id.icon_container);
+        title = (TextView) v.findViewById(R.id.title);
+        text = (TextView) v.findViewById(R.id.text);
+        remoteViewsContainer = (FrameLayout) v.findViewById(R.id.remoteviews);
+        ViewStub rightStub = (ViewStub) v.findViewById(R.id.right_item);
+        if (rightStub != null) {
+            rightStub.setLayoutResource(viewStubLayoutId);
+            rightStub.setInflatedId(R.id.right_item);
+
+            if (viewStubLayoutId == R.layout.car_menu_checkbox) {
+                rightCheckbox = (CheckBox) rightStub.inflate();
+                rightImage = null;
+                rightText = null;
+            } else if (viewStubLayoutId == R.layout.car_imageview) {
+                rightImage = (ImageView) rightStub.inflate();
+                rightCheckbox = null;
+                rightText = null;
+            } else if (viewStubLayoutId == R.layout.car_textview) {
+                rightText = (TextView) rightStub.inflate();
+                rightCheckbox = null;
+                rightImage = null;
+            } else {
+                rightImage = null;
+                rightCheckbox = null;
+                rightText = null;
+            }
+        } else {
+            rightImage = null;
+            rightCheckbox = null;
+            rightText = null;
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/CarRecyclerView.java b/car-stream-ui-lib/src/com/android/car/view/CarRecyclerView.java
new file mode 100644
index 0000000..3e0b9ad
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/CarRecyclerView.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
+ *
+ * It also has the ability to fade children as they scroll off screen that can be set
+ * with {@link #setFadeLastItem(boolean)}.
+ */
+public class CarRecyclerView extends RecyclerView {
+    private static final String PARCEL_CLASS = "android.os.Parcel";
+    private static final String SAVED_STATE_CLASS =
+            "android.support.v7.widget.RecyclerView.SavedState";
+    private boolean mFadeLastItem;
+    private Constructor<?> mSavedStateConstructor;
+    /**
+     * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
+     * called. However, we want to make sure that the list still snaps to the next page when this
+     * happens.
+     */
+    private boolean mWasFlingCalledForGesture;
+
+    public CarRecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setFocusableInTouchMode(false);
+        setFocusable(false);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
+            if (mSavedStateConstructor == null) {
+                mSavedStateConstructor = getSavedStateConstructor();
+            }
+            // Class loader mismatch, recreate from parcel.
+            Parcel obtain = Parcel.obtain();
+            state.writeToParcel(obtain, 0);
+            try {
+                Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain);
+                super.onRestoreInstanceState(newState);
+            } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+                    | InvocationTargetException e) {
+                // Fail loudy here.
+                throw new RuntimeException(e);
+            }
+        } else {
+            super.onRestoreInstanceState(state);
+        }
+    }
+
+    @Override
+    public boolean fling(int velocityX, int velocityY) {
+        mWasFlingCalledForGesture = true;
+        return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        // We want the parent to handle all touch events. There's a lot going on there,
+        // and there is no reason to overwrite that functionality. If we do, bad things will happen.
+        final boolean ret = super.onTouchEvent(e);
+
+        int action = e.getActionMasked();
+        if (action == MotionEvent.ACTION_UP) {
+            if (!mWasFlingCalledForGesture) {
+                ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+            }
+            mWasFlingCalledForGesture = false;
+        }
+
+        return ret;
+    }
+
+    @Override
+    public boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
+        if (mFadeLastItem) {
+            float onScreen = 1f;
+            if ((child.getTop() < getBottom() && child.getBottom() > getBottom())) {
+                onScreen = ((float) (getBottom() - child.getTop())) / (float) child.getHeight();
+            } else if ((child.getTop() < getTop() && child.getBottom() > getTop())) {
+                onScreen = ((float) (child.getBottom() - getTop())) / (float) child.getHeight();
+            }
+            float alpha = 1 - (1 - onScreen) * (1 - onScreen);
+            fadeChild(child, alpha);
+        }
+
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    public void setFadeLastItem(boolean fadeLastItem) {
+        mFadeLastItem = fadeLastItem;
+    }
+
+    public void pageUp() {
+        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        int pageUpPosition = lm.getPageUpPosition();
+        if (pageUpPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageUpPosition);
+    }
+
+    public void pageDown() {
+        CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+        int pageDownPosition = lm.getPageDownPosition();
+        if (pageDownPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageDownPosition);
+    }
+
+    /**
+     * Sets {@link #mSavedStateConstructor} to private SavedState constructor.
+     */
+    private Constructor<?> getSavedStateConstructor() {
+        Class<?> savedStateClass = null;
+        // Find package private subclass RecyclerView$SavedState.
+        for (Class<?> c : RecyclerView.class.getDeclaredClasses()) {
+            if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) {
+                savedStateClass = c;
+                break;
+            }
+        }
+        if (savedStateClass == null) {
+            throw new RuntimeException("RecyclerView$SavedState not found!");
+        }
+        // Find constructor that takes a {@link Parcel}.
+        for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) {
+            Class<?>[] parameterTypes = c.getParameterTypes();
+            if (parameterTypes.length == 1
+                    && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) {
+                mSavedStateConstructor = c;
+                mSavedStateConstructor.setAccessible(true);
+                break;
+            }
+        }
+        if (mSavedStateConstructor == null) {
+            throw new RuntimeException("RecyclerView$SavedState constructor not found!");
+        }
+        return mSavedStateConstructor;
+    }
+
+    /**
+     * Fades child by alpha. If child is a {@link android.view.ViewGroup} then it will recursively fade its
+     * children instead.
+     */
+    private void fadeChild(@NonNull View child, float alpha) {
+        if (child instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) child;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                fadeChild(vg.getChildAt(i), alpha);
+            }
+        } else {
+            child.setAlpha(alpha);
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/CheckboxWrapperView.java b/car-stream-ui-lib/src/com/android/car/view/CheckboxWrapperView.java
new file mode 100644
index 0000000..ecdc0e1
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/CheckboxWrapperView.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+
+/**
+ * A wrapper class for CheckBox that is required because the state is created by two different
+ * class loaders, which causes a crash. When the state and class are created by different class
+ * loaders, the default state will be restored instead of the saved state.
+ * Reflection cannot be used to recreate the state because the class that stores the state
+ * (CompoundButton$SavedState) has been stripped out of the Android SDK.
+ * @hide
+ */
+public class CheckboxWrapperView extends CheckBox {
+
+    public CheckboxWrapperView(Context context) {
+        super(context);
+    }
+
+    public CheckboxWrapperView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CheckboxWrapperView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        // If the class loaders are different, just restore the default state. This is fine
+        // since we refetch the menus anyways and any state will be restored then.
+        if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
+            super.onRestoreInstanceState(onSaveInstanceState());
+        } else {
+            super.onRestoreInstanceState(state);
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/MaxWidthLayout.java b/car-stream-ui-lib/src/com/android/car/view/MaxWidthLayout.java
new file mode 100644
index 0000000..0b96b5c
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/MaxWidthLayout.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.car.stream.ui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Acts as a container to make the width of all its children not larger than the setup max width.
+ *
+ * To use MaxWidthLayout, put it as the outermost layout of all children you want to limit its max
+ * width and set the maxWidth appropriately.
+ */
+public class MaxWidthLayout extends FrameLayout {
+
+    // If mMaxChildrenWidth == 0, it means that it doesn't set the max width, just use the current
+    // width directly.
+    private int mMaxChildrenWidth;
+
+    public MaxWidthLayout(Context context) {
+        super(context);
+        initialize(context.obtainStyledAttributes(R.styleable.MaxWidthLayout));
+    }
+
+    public MaxWidthLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize(context.obtainStyledAttributes(attrs, R.styleable.MaxWidthLayout));
+    }
+
+    public MaxWidthLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(context
+                .obtainStyledAttributes(attrs, R.styleable.MaxWidthLayout, defStyleAttr, 0));
+    }
+
+    /**
+     * Initialize MaxWidthLayout specific attributes and recycle the TypeArray.
+     */
+    private void initialize(TypedArray ta) {
+        mMaxChildrenWidth = (int) (ta.getDimension(R.styleable.MaxWidthLayout_carMaxWidth, 0));
+        ta.recycle();
+    }
+
+    /**
+     * Re-measure the child width if it is greater than max width.
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mMaxChildrenWidth == 0) {
+            return;
+        }
+
+        final List<View> matchParentChildren = new ArrayList<View>();
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    matchParentChildren.add(child);
+                }
+            }
+        }
+        for (int i = matchParentChildren.size() - 1; i >= 0; --i) {
+            final View child = matchParentChildren.get(i);
+            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+            if (child.getMeasuredWidth() > mMaxChildrenWidth) {
+                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        mMaxChildrenWidth - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
+                int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        child.getMeasuredHeight(), MeasureSpec.EXACTLY);
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+            }
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/PagedListView.java b/car-stream-ui-lib/src/com/android/car/view/PagedListView.java
new file mode 100644
index 0000000..863a358
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/PagedListView.java
@@ -0,0 +1,544 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.car.stream.ui.R;
+
+/**
+ * Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
+ * resembles a {@link android.widget.ListView} but also has page up and page down arrows
+ * on the right side.
+ */
+public class PagedListView extends FrameLayout {
+    private static final String TAG = "PagedListView";
+
+    /**
+     * The amount of time after settling to wait before autoscrolling to the next page when the
+     * user holds down a pagination button.
+     */
+    private static final int PAGINATION_HOLD_DELAY_MS = 400;
+
+    private final CarRecyclerView mRecyclerView;
+    private final CarLayoutManager mLayoutManager;
+    private final PagedScrollBarView mScrollBarView;
+    private final Handler mHandler = new Handler();
+    private Decoration mDecor = new Decoration(getContext());
+
+    /** Maximum number of pages to show. Values < 0 show all pages. */
+    private int mMaxPages = -1;
+    /** Number of visible rows per page */
+    private int mRowsPerPage = -1;
+
+    /**
+     * Used to check if there are more items added to the list.
+     */
+    private int mLastItemCount = 0;
+
+    private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
+
+    private boolean mNeedsFocus;
+    private OnScrollBarListener mOnScrollBarListener;
+
+    /**
+     * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the
+     * number of items.
+     * <p>NOTE: it is still up to the adapter to use maxItems in
+     * {@link android.support.v7.widget.RecyclerView.Adapter#getItemCount()}.
+     *
+     * the recommended way would be with:
+     * <pre>
+     * @Override
+     * public int getItemCount() {
+     *     return Math.min(super.getItemCount(), mMaxItems);
+     * }
+     * </pre>
+     */
+    public interface ItemCap {
+        public static final int UNLIMITED = -1;
+
+        /**
+         * Sets the maximum number of items available in the adapter. A value less than '0'
+         * means the list should not be capped.
+         */
+        void setMaxItems(int maxItems);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes);
+        boolean rightGutterEnabled =
+                a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false);
+        LayoutInflater.from(context)
+                .inflate(R.layout.car_paged_recycler_view, this /*root*/, true /*attachToRoot*/);
+        if (rightGutterEnabled) {
+            FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.max_width_layout);
+            LayoutParams params =
+                    (LayoutParams) maxWidthLayout.getLayoutParams();
+            params.rightMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin);
+            maxWidthLayout.setLayoutParams(params);
+        }
+        mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view);
+        boolean fadeLastItem = a.getBoolean(R.styleable.PagedListView_fadeLastItem, false);
+        mRecyclerView.setFadeLastItem(fadeLastItem);
+        boolean offsetRows = a.getBoolean(R.styleable.PagedListView_offsetRows, false);
+        a.recycle();
+
+        mMaxPages = getDefaultMaxPages();
+
+        mLayoutManager = new CarLayoutManager(context);
+        mLayoutManager.setOffsetRows(offsetRows);
+        mLayoutManager.setItemsChangedListener(mItemsChangedListener);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.addItemDecoration(mDecor);
+        mRecyclerView.setOnScrollListener(mOnScrollListener);
+        mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
+        mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+
+        mScrollBarView = (PagedScrollBarView) findViewById(R.id.paged_scroll_view);
+        mScrollBarView.setPaginationListener(new PagedScrollBarView.PaginationListener() {
+            @Override
+            public void onPaginate(int direction) {
+                if (direction == PagedScrollBarView.PaginationListener.PAGE_UP) {
+                    mRecyclerView.pageUp();
+                } else if (direction == PagedScrollBarView.PaginationListener.PAGE_DOWN) {
+                    mRecyclerView.pageDown();
+                } else {
+                    Log.e(TAG, "Unknown pagination direction (" + direction + ")");
+                }
+            }
+        });
+
+        setAutoDayNightMode();
+        updatePaginationButtons(false /*animate*/);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mHandler.removeCallbacks(mUpdatePaginationRunnable);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN) {
+            // The user has interacted with the list using touch. All movements will now paginate
+            // the list.
+            mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE);
+        }
+        return super.onInterceptTouchEvent(e);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        // The user has interacted with the list using the controller. Movements through the list
+        // will now be one row at a time.
+        mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
+    }
+
+    public int positionOf(@Nullable View v) {
+        if (v == null || v.getParent() != mRecyclerView) {
+            return -1;
+        }
+        return mLayoutManager.getPosition(v);
+    }
+
+    @NonNull
+    public CarRecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    public void scrollToPosition(int position) {
+        mLayoutManager.scrollToPosition(position);
+
+        // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
+        // the pagination arrows actually get updated.
+        mHandler.post(mUpdatePaginationRunnable);
+    }
+
+    /**
+     * Sets the adapter for the list.
+     * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw
+     * an {@link IllegalArgumentException}.
+     */
+    public void setAdapter(
+            @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
+        if (!(adapter instanceof ItemCap)) {
+            throw new IllegalArgumentException("ERROR: adapter "
+                    + "[" + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap");
+        }
+
+        mAdapter = adapter;
+        mRecyclerView.setAdapter(adapter);
+        tryUpdateMaxPages();
+    }
+
+    @NonNull
+    public CarLayoutManager getLayoutManager() {
+        return mLayoutManager;
+    }
+
+    @Nullable
+    @SuppressWarnings("unchecked")
+    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() {
+        return mRecyclerView.getAdapter();
+    }
+
+    public void setMaxPages(int maxPages) {
+        mMaxPages = maxPages;
+        tryUpdateMaxPages();
+    }
+
+    public int getMaxPages() {
+        return mMaxPages;
+    }
+
+    public void resetMaxPages() {
+        mMaxPages = getDefaultMaxPages();
+    }
+
+    public void setDefaultItemDecoration(Decoration decor) {
+        removeDefaultItemDecoration();
+        mDecor = decor;
+        addItemDecoration(mDecor);
+    }
+
+    public void removeDefaultItemDecoration() {
+        mRecyclerView.removeItemDecoration(mDecor);
+    }
+
+    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.addItemDecoration(decor);
+    }
+
+    public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.removeItemDecoration(decor);
+    }
+
+    public void setAutoDayNightMode() {
+        mScrollBarView.setAutoDayNightMode();
+        mDecor.updateDividerColor();
+    }
+
+    public void setLightMode() {
+        mScrollBarView.setLightMode();
+        mDecor.updateDividerColor();
+    }
+
+    public void setDarkMode() {
+        mScrollBarView.setDarkMode();
+        mDecor.updateDividerColor();
+    }
+
+    public void setOnScrollBarListener(OnScrollBarListener listener) {
+        mOnScrollBarListener = listener;
+    }
+
+    /** Returns the page the given position is on, starting with page 0. */
+    public int getPage(int position) {
+        if (mRowsPerPage == -1) {
+            return -1;
+        }
+        return position / mRowsPerPage;
+    }
+
+    /** Returns the default number of pages the list should have */
+    protected int getDefaultMaxPages() {
+        // assume list shown in response to a click, so, reduce number of clicks by one
+        //return ProjectionUtils.getMaxClicks(getContext().getContentResolver()) - 1;
+        return 5;
+    }
+
+    private void tryUpdateMaxPages() {
+        if (mAdapter == null) {
+            return;
+        }
+
+        View firstChild = mLayoutManager.getChildAt(0);
+        int firstRowHeight = firstChild == null ? 0 : firstChild.getHeight();
+        mRowsPerPage = firstRowHeight == 0 ? 1 : getHeight() / firstRowHeight;
+
+        int newMaxItems;
+        if (mMaxPages < 0) {
+            newMaxItems = -1;
+        } else if (mMaxPages == 0) {
+            // At the last click of 6 click limit, we show one more warning item at the top of menu.
+            newMaxItems = mRowsPerPage + 1;
+        } else {
+            newMaxItems = mRowsPerPage * mMaxPages;
+        }
+
+        int originalCount = mAdapter.getItemCount();
+        ((ItemCap) mAdapter).setMaxItems(newMaxItems);
+        int newCount = mAdapter.getItemCount();
+        if (newCount < originalCount) {
+            mAdapter.notifyItemRangeChanged(newCount, originalCount);
+        } else if (newCount > originalCount) {
+            mAdapter.notifyItemInserted(originalCount);
+        }
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        // if a late item is added to the top of the layout after the layout is stabilized, causing
+        // the former top item to be pushed to the 2nd page, the focus will still be on the former
+        // top item. Since our car layout manager tries to scroll the viewport so that the focused
+        // item is visible, the view port will be on the 2nd page. That means the newly added item
+        // will not be visible, on the first page.
+
+        // what we want to do is: if the formerly focused item is the first one in the list, any
+        // item added above it will make the focus to move to the new first item.
+        // if the focus is not on the formerly first item, then we don't need to do anything. Let
+        // the layout manager do the job and scroll the viewport so the currently focused item
+        // is visible.
+
+        // we need to calculate whether we want to request focus here, before the super call,
+        // because after the super call, the first born might be changed.
+        View focusedChild = mLayoutManager.getFocusedChild();
+        View firstBorn = mLayoutManager.getChildAt(0);
+
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mAdapter != null) {
+            int itemCount = mAdapter.getItemCount();
+           // if () {
+                Log.d(TAG, String.format(
+                        "onLayout hasFocus: %s, mLastItemCount: %s, itemCount: %s, focusedChild: " +
+                                "%s, firstBorn: %s, isInTouchMode: %s, mNeedsFocus: %s",
+                        hasFocus(), mLastItemCount, itemCount, focusedChild, firstBorn,
+                        isInTouchMode(), mNeedsFocus));
+          //  }
+            tryUpdateMaxPages();
+            // This is a workaround for missing focus because isInTouchMode() is not always
+            // returning the right value.
+            // This is okay for the Engine release since focus is always showing.
+            // However, in Tala and Fender, we want to show focus only when the user uses
+            // hardware controllers, so we need to revisit this logic. b/22990605.
+            if (mNeedsFocus && itemCount > 0) {
+                if (focusedChild == null) {
+                    requestFocusFromTouch();
+                }
+                mNeedsFocus = false;
+            }
+            if (itemCount > mLastItemCount && focusedChild == firstBorn &&
+                    getContext().getResources().getBoolean(R.bool.has_wheel)) {
+                requestFocusFromTouch();
+            }
+            mLastItemCount = itemCount;
+        }
+        updatePaginationButtons(true /*animate*/);
+    }
+
+    @Override
+    public boolean requestFocus(int direction, Rect rect) {
+        if (getContext().getResources().getBoolean(R.bool.has_wheel)) {
+            mNeedsFocus = true;
+        }
+        return super.requestFocus(direction, rect);
+    }
+
+    public View findViewByPosition(int position) {
+        return mLayoutManager.findViewByPosition(position);
+    }
+
+    private void updatePaginationButtons(boolean animate) {
+        boolean isAtTop = mLayoutManager.isAtTop();
+        boolean isAtBottom = mLayoutManager.isAtBottom();
+        if (isAtTop && isAtBottom) {
+            mScrollBarView.setVisibility(View.INVISIBLE);
+        } else {
+            mScrollBarView.setVisibility(View.VISIBLE);
+        }
+        mScrollBarView.setUpEnabled(!isAtTop);
+        mScrollBarView.setDownEnabled(!isAtBottom);
+
+        mScrollBarView.setParameters(
+                mRecyclerView.computeVerticalScrollRange(),
+                mRecyclerView.computeVerticalScrollOffset(),
+                mRecyclerView.computeVerticalScrollExtent(),
+                animate);
+        invalidate();
+    }
+
+    private final RecyclerView.OnScrollListener mOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+
+        @Override
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+            if (mOnScrollBarListener != null) {
+                if (!mLayoutManager.isAtTop() && mLayoutManager.isAtBottom()) {
+                    mOnScrollBarListener.onReachBottom();
+                }
+                if (mLayoutManager.isAtTop() || !mLayoutManager.isAtBottom()) {
+                    mOnScrollBarListener.onLeaveBottom();
+                }
+            }
+            updatePaginationButtons(false);
+        }
+
+        @Override
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+            }
+        }
+    };
+    private final Runnable mPaginationRunnable = new Runnable() {
+        @Override
+        public void run() {
+            boolean upPressed = mScrollBarView.isUpPressed();
+            boolean downPressed = mScrollBarView.isDownPressed();
+            if (upPressed && downPressed) {
+                // noop
+            } else if (upPressed) {
+                mRecyclerView.pageUp();
+            } else if (downPressed) {
+                mRecyclerView.pageDown();
+            }
+        }
+    };
+
+    private final Runnable mUpdatePaginationRunnable = new Runnable() {
+        @Override
+        public void run() {
+            updatePaginationButtons(true /*animate*/);
+        }
+    };
+
+    private final CarLayoutManager.OnItemsChangedListener mItemsChangedListener =
+            new CarLayoutManager.OnItemsChangedListener() {
+                @Override
+                public void onItemsChanged() {
+                    updatePaginationButtons(true /*animate*/);
+                }
+            };
+
+    abstract static public class OnScrollBarListener {
+        public void onReachBottom() {}
+        public void onLeaveBottom() {}
+    }
+
+    public static class Decoration extends RecyclerView.ItemDecoration {
+        protected final Paint mPaint;
+        protected final int mDividerHeight;
+        protected final Context mContext;
+
+
+        public Decoration(Context context) {
+            mContext = context;
+            mPaint = new Paint();
+            updateDividerColor();
+            mDividerHeight = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.car_divider_height);
+        }
+
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            final int left = getLeft(parent.getChildAt(0));
+            final int right = parent.getWidth() - parent.getPaddingRight();
+            int top;
+            int bottom;
+
+            c.drawRect(left, 0, right, mDividerHeight, mPaint);
+
+            final int childCount = parent.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                final View child = parent.getChildAt(i);
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+                        .getLayoutParams();
+                bottom = child.getBottom() - params.bottomMargin;
+                top = bottom - mDividerHeight;
+                if (top > 0) {
+                    c.drawRect(left, top, right, bottom, mPaint);
+                }
+            }
+        }
+
+        /**
+         * Updates the list divider color which may have changed due to a day night transition.
+         */
+        public void updateDividerColor() {
+            mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider));
+        }
+
+        /**
+         * Find the left edge of the decoration line. It should be left aligned with the left edge
+         * of the first {@link android.widget.TextView}.
+         */
+        private int getLeft(View root) {
+            if (root == null) {
+                return 0;
+            }
+            View view = findTextView(root);
+            if (view == null) {
+                view = root;
+            }
+            int left = 0;
+            while (view != null && view != root) {
+                left += view.getLeft();
+                view = (View) view.getParent();
+            }
+            return left;
+        }
+
+        private TextView findTextView(View root) {
+            if (root == null) {
+                return null;
+            }
+            if (root instanceof TextView) {
+                return (TextView) root;
+            }
+            if (root instanceof ViewGroup) {
+                ViewGroup parent = (ViewGroup) root;
+                final int childCount = parent.getChildCount();
+                for(int i = 0; i < childCount; i++) {
+                    TextView tv = findTextView(parent.getChildAt(i));
+                    if (tv != null) {
+                        return tv;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/car-stream-ui-lib/src/com/android/car/view/PagedScrollBarView.java b/car-stream-ui-lib/src/com/android/car/view/PagedScrollBarView.java
new file mode 100644
index 0000000..b5278d7
--- /dev/null
+++ b/car-stream-ui-lib/src/com/android/car/view/PagedScrollBarView.java
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+package com.android.car.view;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.car.stream.ui.R;
+
+/**
+ * A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator.
+ *
+ * @hide
+ */
+public class PagedScrollBarView extends FrameLayout
+        implements View.OnClickListener, View.OnLongClickListener {
+    private static final float BUTTON_DISABLED_ALPHA = 0.2f;
+
+    /**
+     * Listener for when the list should paginate.
+     */
+    public interface PaginationListener {
+        int PAGE_UP = 0;
+        int PAGE_DOWN = 1;
+
+        /** Called when the linked view should be paged in the given direction */
+        void onPaginate(int direction);
+    }
+
+    private final ImageView mUpButton;
+    private final ImageView mDownButton;
+    private final ImageView mScrollThumb;
+    /** The "filler" view between the up and down buttons */
+    private final View mFiller;
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+    private final int mMinThumbLength;
+    private final int mMaxThumbLength;
+    private PaginationListener mPaginationListener;
+
+    public PagedScrollBarView(
+            Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(
+            Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(
+            Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(
+                R.layout.car_paged_scrollbar_buttons, this /*root*/, true /*attachToRoot*/);
+
+        mUpButton = (ImageView) findViewById(R.id.page_up);
+        mUpButton.setImageDrawable(context.getDrawable(R.drawable.ic_up_button));
+        mUpButton.setOnClickListener(this);
+        mUpButton.setOnLongClickListener(this);
+        mDownButton = (ImageView) findViewById(R.id.page_down);
+        mDownButton.setImageDrawable(context.getDrawable(R.drawable.ic_down_button));
+        mDownButton.setOnClickListener(this);
+        mDownButton.setOnLongClickListener(this);
+
+        mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
+        mScrollThumb.setAlpha(0.5f);
+
+        mFiller = findViewById(R.id.filler);
+
+        mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height);
+        mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height);
+
+        if (!context.getResources().getBoolean(R.bool.car_true_for_touch)) {
+            // Don't show the pagination buttons if there isn't touch.
+            mUpButton.setVisibility(View.GONE);
+            mDownButton.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        dispatchPageClick(v);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        dispatchPageClick(v);
+        return true;
+    }
+
+    public void setPaginationListener(PaginationListener listener) {
+        mPaginationListener = listener;
+    }
+
+    /** Returns {@code true} if the "up" button is pressed */
+    public boolean isUpPressed() {
+        return mUpButton.isPressed();
+    }
+
+    /** Returns {@code true} if the "down" button is pressed */
+    public boolean isDownPressed() {
+        return mDownButton.isPressed();
+    }
+
+    /** Sets the range, offset and extent of the scroll bar. See {@link android.view.View}. */
+    protected void setParameters(int range, int offset, int extent, boolean animate) {
+        final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
+
+        int thumbLength = extent * size / range;
+        thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength);
+
+        int thumbOffset = size - thumbLength;
+        if (isDownEnabled()) {
+            // We need to adjust the offset so that it fits into the possible space inside the
+            // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength.
+            thumbOffset = (size - thumbLength) * offset / range;
+        }
+
+        // Sets the size of the thumb and request a redraw if needed.
+        final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
+        if (lp.height != thumbLength) {
+            lp.height = thumbLength;
+            mScrollThumb.requestLayout();
+        }
+
+        moveY(mScrollThumb, thumbOffset, animate);
+    }
+
+    /** Sets auto day/night mode */
+    protected void setAutoDayNightMode() {
+        int color = getResources().getColor(R.color.car_tint);
+        mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mUpButton.setBackgroundResource(R.drawable.car_pagination_background);
+        mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mDownButton.setBackgroundResource(R.drawable.car_pagination_background);
+
+        mScrollThumb.setBackgroundColor(color);
+    }
+
+    /** Sets auto light mode */
+    protected void setLightMode() {
+        int color = getResources().getColor(R.color.car_tint_light);
+        mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mUpButton.setBackgroundResource(R.drawable.car_pagination_background_light);
+        mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mDownButton.setBackgroundResource(R.drawable.car_pagination_background_light);
+
+        mScrollThumb.setBackgroundColor(color);
+    }
+
+    /** Sets auto dark mode */
+    protected void setDarkMode() {
+        int color = getResources().getColor(R.color.car_tint_dark);
+        mUpButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mUpButton.setBackgroundResource(R.drawable.car_pagination_background_dark);
+        mDownButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+        mDownButton.setBackgroundResource(R.drawable.car_pagination_background_dark);
+
+        mScrollThumb.setBackgroundColor(color);
+    }
+
+    protected void setUpEnabled(boolean enabled) {
+        mUpButton.setEnabled(enabled);
+        mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    protected void setDownEnabled(boolean enabled) {
+        mDownButton.setEnabled(enabled);
+        mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    protected boolean isDownEnabled() {
+        return mDownButton.isEnabled();
+    }
+
+    private void dispatchPageClick(View v) {
+        final PaginationListener listener = mPaginationListener;
+        if (listener == null) {
+            return;
+        }
+
+        final int direction = (v.getId() == R.id.page_up)
+                ? PaginationListener.PAGE_UP : PaginationListener.PAGE_DOWN;
+        listener.onPaginate(direction);
+    }
+
+    /** Moves the given view to the specified 'y' position. */
+    private void moveY(final View view, float newPosition, boolean animate) {
+        final int duration = animate ? 200 : 0;
+        view.animate()
+                .y(newPosition)
+                .setDuration(duration)
+                .setInterpolator(mPaginationInterpolator)
+                .start();
+    }
+}