Merge "Add PreferenceDialogFragments for Chassis." into pi-car-dev
diff --git a/car-apps-common/res/values/colors.xml b/car-apps-common/res/values/colors.xml
index 3104f1d..37a7bda 100644
--- a/car-apps-common/res/values/colors.xml
+++ b/car-apps-common/res/values/colors.xml
@@ -34,6 +34,7 @@
         <item>#757575</item>
     </array>
 
+    <color name="loading_image_placeholder_color">@*android:color/car_grey_800</color>
     <color name="improper_image_refs_tint_color">#C8FF0000</color>
 
     <color name="control_bar_background_color">@android:color/transparent</color>
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java b/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
index 63b276e..0e59a54 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/ImageBinder.java
@@ -22,10 +22,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.util.Size;
 
+import com.android.car.apps.common.R;
 import com.android.car.apps.common.UriUtils;
 
 import java.util.Objects;
@@ -75,6 +77,7 @@
     private T mCurrentRef;
     private ImageKey mCurrentKey;
     private BiConsumer<ImageKey, Drawable> mFetchReceiver;
+    private Drawable mLoadingDrawable;
 
 
     public ImageBinder(@NonNull PlaceholderType type, @NonNull Size maxImageSize,
@@ -155,10 +158,19 @@
             getImageFetcher(context).cancelRequest(mCurrentKey, mFetchReceiver);
             onRequestFinished();
         }
+        setDrawable(getLoadingDrawable(context));
     }
 
     private void onRequestFinished() {
         mCurrentKey = null;
         mFetchReceiver = null;
     }
+
+    private Drawable getLoadingDrawable(Context context) {
+        if (mLoadingDrawable == null) {
+            int color = context.getColor(R.color.loading_image_placeholder_color);
+            mLoadingDrawable = new ColorDrawable(color);
+        }
+        return mLoadingDrawable;
+    }
 }
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
index db6ee1a..f346de0 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
@@ -82,10 +82,11 @@
 
     @Override
     protected void prepareForNewBinding(Context context) {
-        super.prepareForNewBinding(context);
         mImageView.setImageBitmap(null);
         mImageView.setImageDrawable(null);
         mImageView.clearColorFilter();
+        // Call super last to setup the default loading drawable.
+        super.prepareForNewBinding(context);
     }
 
 }
diff --git a/car-chassis-lib/.gitignore b/car-chassis-lib/.gitignore
new file mode 100644
index 0000000..fa8bef3
--- /dev/null
+++ b/car-chassis-lib/.gitignore
@@ -0,0 +1,11 @@
+# Local configuration
+local.properties
+gradle-wrapper.properties
+
+# Gradle
+gradle/
+.gradle/
+build/
+
+# IntelliJ
+*.iml
\ No newline at end of file
diff --git a/car-chassis-lib/AndroidManifest-gradle.xml b/car-chassis-lib/AndroidManifest-gradle.xml
new file mode 100644
index 0000000..6844663
--- /dev/null
+++ b/car-chassis-lib/AndroidManifest-gradle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.chassis">
+</manifest>
diff --git a/car-chassis-lib/README.md b/car-chassis-lib/README.md
index b3e3c1c..3d1015a 100644
--- a/car-chassis-lib/README.md
+++ b/car-chassis-lib/README.md
@@ -19,10 +19,10 @@
 
 Here is the process for updating this library:
 
-1. Develop, test and create CL in Gerrit with the desired changes
-2. On Google3, run update.sh and test your changes
-3. Iterate until your changes look okay on both places.
-4. Back on Gerrit, submit your CL
-5. Back on Google3, run update.sh again and submit
+1. Develop, test and upload changes to Gerrit
+2. On Google3, run './update.sh review <cl>' (with <cl> being your Gerrit CL #) and test your changes
+3. Repeat #1 and #2 until your changes look okay on both places.
+4. Back on Gerrit, submit your CL.
+5. Back on Google3, run './update.sh manual' submit
 
 TODO: Automate this process using CaaS (in progress)
diff --git a/car-chassis-lib/build.gradle b/car-chassis-lib/build.gradle
new file mode 100644
index 0000000..21b2b0d
--- /dev/null
+++ b/car-chassis-lib/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+// Library-level build file
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion 28
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest-gradle.xml'
+            java.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+}
+
+dependencies {
+    implementation 'androidx.annotation:annotation:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    implementation 'androidx.recyclerview:recyclerview:1.0.0'
+}
diff --git a/car-chassis-lib/gradle.properties b/car-chassis-lib/gradle.properties
new file mode 100644
index 0000000..9dad1c4
--- /dev/null
+++ b/car-chassis-lib/gradle.properties
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2019 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.
+
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
\ No newline at end of file
diff --git a/car-chassis-lib/gradlew b/car-chassis-lib/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/car-chassis-lib/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/car-chassis-lib/gradlew.bat b/car-chassis-lib/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/car-chassis-lib/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/car-chassis-lib/res/drawable/ic_arrow_back.xml b/car-chassis-lib/res/drawable/chassis_icon_arrow_back.xml
similarity index 100%
rename from car-chassis-lib/res/drawable/ic_arrow_back.xml
rename to car-chassis-lib/res/drawable/chassis_icon_arrow_back.xml
diff --git a/car-chassis-lib/res/drawable/ic_close.xml b/car-chassis-lib/res/drawable/chassis_icon_close.xml
similarity index 91%
rename from car-chassis-lib/res/drawable/ic_close.xml
rename to car-chassis-lib/res/drawable/chassis_icon_close.xml
index 48ab552..482df0f 100644
--- a/car-chassis-lib/res/drawable/ic_close.xml
+++ b/car-chassis-lib/res/drawable/chassis_icon_close.xml
@@ -17,8 +17,8 @@
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/primary_icon_size"
-    android:height="@dimen/primary_icon_size"
+    android:width="24dp"
+    android:height="24dp"
     android:viewportHeight="24.0"
     android:viewportWidth="24.0">
     <path
diff --git a/car-chassis-lib/res/drawable/ic_search.xml b/car-chassis-lib/res/drawable/chassis_icon_search.xml
similarity index 92%
rename from car-chassis-lib/res/drawable/ic_search.xml
rename to car-chassis-lib/res/drawable/chassis_icon_search.xml
index 87e7d46..f70e61e 100644
--- a/car-chassis-lib/res/drawable/ic_search.xml
+++ b/car-chassis-lib/res/drawable/chassis_icon_search.xml
@@ -14,8 +14,8 @@
 limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/primary_icon_size"
-    android:height="@dimen/primary_icon_size"
+    android:width="48dp"
+    android:height="48dp"
     android:viewportWidth="48"
     android:viewportHeight="48">
 
diff --git a/car-chassis-lib/res/drawable/ic_settings.xml b/car-chassis-lib/res/drawable/chassis_icon_settings.xml
similarity index 94%
rename from car-chassis-lib/res/drawable/ic_settings.xml
rename to car-chassis-lib/res/drawable/chassis_icon_settings.xml
index 5c9e6a7..ebf8576 100644
--- a/car-chassis-lib/res/drawable/ic_settings.xml
+++ b/car-chassis-lib/res/drawable/chassis_icon_settings.xml
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/primary_icon_size"
-        android:height="@dimen/primary_icon_size"
+        android:width="24dp"
+        android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
 
diff --git a/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_button_ripple_background.xml b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_button_ripple_background.xml
new file mode 100644
index 0000000..b5f107c
--- /dev/null
+++ b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_button_ripple_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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/chassis_card_ripple_background" />
diff --git a/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_divider.xml b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_divider.xml
new file mode 100644
index 0000000..bddaae3
--- /dev/null
+++ b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_divider.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size android:height="0dp" />
+    <solid android:color="@android:color/transparent" />
+</shape>
diff --git a/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_down.xml b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_down.xml
new file mode 100644
index 0000000..380bf46
--- /dev/null
+++ b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_down.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,16.42L24,25.59l9.17,-9.17L36,19.25l-12,12 -12,-12z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_up.xml b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_up.xml
new file mode 100644
index 0000000..2eff62f
--- /dev/null
+++ b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_ic_up.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M14.83,30.83L24,21.66l9.17,9.17L36,28 24,16 12,28z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_scrollbar_thumb.xml b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_scrollbar_thumb.xml
new file mode 100644
index 0000000..9180f1a
--- /dev/null
+++ b/car-chassis-lib/res/drawable/chassis_pagedrecyclerview_scrollbar_thumb.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/chassis_scrollbar_thumb" />
+    <corners android:radius="@dimen/chassis_scrollbar_thumb_radius"/>
+</shape>
diff --git a/car-chassis-lib/res/drawable/chassis_toolbar_button_background.xml b/car-chassis-lib/res/drawable/chassis_toolbar_button_background.xml
index 7d29d64..efa63b9 100644
--- a/car-chassis-lib/res/drawable/chassis_toolbar_button_background.xml
+++ b/car-chassis-lib/res/drawable/chassis_toolbar_button_background.xml
@@ -17,5 +17,5 @@
   ~
  -->
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="@*android:color/car_card_ripple_background"
+        android:color="@color/chassis_card_ripple_background"
         android:radius="@dimen/chassis_toolbar_button_background_radius"/>
diff --git a/car-chassis-lib/res/drawable/divider.xml b/car-chassis-lib/res/drawable/divider.xml
new file mode 100644
index 0000000..164b71a
--- /dev/null
+++ b/car-chassis-lib/res/drawable/divider.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <size android:height="2dp"
+          android:width="2dp"/>
+    <solid android:color="@android:color/transparent" />
+</shape>
diff --git a/car-chassis-lib/res/layout/chassis_paged_recycler_view_item.xml b/car-chassis-lib/res/layout/chassis_paged_recycler_view_item.xml
new file mode 100644
index 0000000..6a35b43
--- /dev/null
+++ b/car-chassis-lib/res/layout/chassis_paged_recycler_view_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/nested_recycler_view_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center">
+</FrameLayout>
diff --git a/car-chassis-lib/res/layout/chassis_pagedrecyclerview_scrollbar.xml b/car-chassis-lib/res/layout/chassis_pagedrecyclerview_scrollbar.xml
new file mode 100644
index 0000000..7678940
--- /dev/null
+++ b/car-chassis-lib/res/layout/chassis_pagedrecyclerview_scrollbar.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center">
+
+    <ImageButton
+        android:id="@+id/page_up"
+        android:layout_width="@dimen/chassis_scrollbar_button_size"
+        android:layout_height="@dimen/chassis_scrollbar_button_size"
+        android:background="@drawable/chassis_pagedrecyclerview_button_ripple_background"
+        android:contentDescription="@string/chassis_scrollbar_page_up_button"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:src="@drawable/chassis_pagedrecyclerview_ic_up"
+        android:scaleType="centerInside" />
+
+    <!-- View height is dynamically calculated during layout. -->
+    <View
+        android:id="@+id/scrollbar_thumb"
+        android:layout_width="@dimen/chassis_scrollbar_thumb_width"
+        android:layout_height="0dp"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/chassis_pagedrecyclerview_scrollbar_thumb" />
+
+    <ImageButton
+        android:id="@+id/page_down"
+        android:layout_width="@dimen/chassis_scrollbar_button_size"
+        android:layout_height="@dimen/chassis_scrollbar_button_size"
+        android:background="@drawable/chassis_pagedrecyclerview_button_ripple_background"
+        android:contentDescription="@string/chassis_scrollbar_page_down_button"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:src="@drawable/chassis_pagedrecyclerview_ic_down"
+        android:scaleType="centerInside" />
+</LinearLayout>
diff --git a/car-chassis-lib/res/layout/chassis_search_view.xml b/car-chassis-lib/res/layout/chassis_search_view.xml
index e9942a7..7decb3a 100644
--- a/car-chassis-lib/res/layout/chassis_search_view.xml
+++ b/car-chassis-lib/res/layout/chassis_search_view.xml
@@ -21,9 +21,9 @@
 
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="@dimen/touch_target_size"
-        android:layout_height="@dimen/touch_target_size"
-        android:src="@drawable/ic_search"
+        android:layout_width="@dimen/chassis_touch_target_width"
+        android:layout_height="@dimen/chassis_touch_target_height"
+        android:src="@drawable/chassis_icon_search"
         android:scaleType="center"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -33,9 +33,9 @@
         android:id="@+id/search_bar"
         android:layout_height="match_parent"
         android:layout_width="match_parent"
-        android:paddingLeft="@dimen/touch_target_size"
-        android:hint="@string/chassis_default_search_hint"
-        android:textColorHint="@color/search_hint_text_color"
+        android:paddingLeft="@dimen/chassis_touch_target_width"
+        android:hint="@string/chassis_toolbar_default_search_hint"
+        android:textColorHint="@color/chassis_toolbar_search_hint_text_color"
         android:inputType="text"
         android:singleLine="true"
         android:imeOptions="actionDone"
@@ -46,10 +46,10 @@
 
     <ImageView
         android:id="@+id/search_close"
-        android:layout_width="@dimen/touch_target_size"
-        android:layout_height="@dimen/touch_target_size"
+        android:layout_width="@dimen/chassis_touch_target_width"
+        android:layout_height="@dimen/chassis_touch_target_height"
         android:background="@drawable/chassis_toolbar_button_background"
-        android:src="@drawable/ic_close"
+        android:src="@drawable/chassis_icon_close"
         android:scaleType="center"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
diff --git a/car-chassis-lib/res/layout/chassis_toolbar_search_button.xml b/car-chassis-lib/res/layout/chassis_toolbar_search_button.xml
index 91afe95..e42e4ce 100644
--- a/car-chassis-lib/res/layout/chassis_toolbar_search_button.xml
+++ b/car-chassis-lib/res/layout/chassis_toolbar_search_button.xml
@@ -17,8 +17,8 @@
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/search"
-    android:layout_width="@dimen/touch_target_size"
-    android:layout_height="@dimen/touch_target_size"
-    android:src="@drawable/ic_search"
+    android:layout_width="@dimen/chassis_touch_target_width"
+    android:layout_height="@dimen/chassis_touch_target_height"
+    android:src="@drawable/chassis_icon_search"
     android:scaleType="center"
     android:background="@drawable/chassis_toolbar_button_background"/>
diff --git a/car-chassis-lib/res/layout/chassis_toolbar_settings_button.xml b/car-chassis-lib/res/layout/chassis_toolbar_settings_button.xml
index c295d5c..f044099 100644
--- a/car-chassis-lib/res/layout/chassis_toolbar_settings_button.xml
+++ b/car-chassis-lib/res/layout/chassis_toolbar_settings_button.xml
@@ -17,8 +17,8 @@
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/settings"
-    android:layout_width="@dimen/touch_target_size"
-    android:layout_height="@dimen/touch_target_size"
-    android:src="@drawable/ic_settings"
+    android:layout_width="@dimen/chassis_touch_target_width"
+    android:layout_height="@dimen/chassis_touch_target_height"
+    android:src="@drawable/chassis_icon_settings"
     android:scaleType="center"
     android:background="@drawable/chassis_toolbar_button_background"/>
diff --git a/car-chassis-lib/res/values-night/colors.xml b/car-chassis-lib/res/values-night/colors.xml
index 0ee4613..6e0aebe 100644
--- a/car-chassis-lib/res/values-night/colors.xml
+++ b/car-chassis-lib/res/values-night/colors.xml
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2019 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.
@@ -14,6 +14,12 @@
      limitations under the License.
 -->
 <resources>
-    <color name="chassis_tab_selected_color">@color/chassis_tab_selected_color_dark</color>
-    <color name="chassis_tab_unselected_color">@color/chassis_tab_unselected_color_dark</color>
+    <!-- General -->
+
+    <!-- Main text color (titles, text body, etc.) -->
+    <color name="chassis_primary_text_color">#E2FFFFFF</color>
+    <!-- Text color used in subtitles or other secondary text blocks -->
+    <color name="chassis_secondary_text_color">#80FFFFFF</color>
+    <!-- The ripple color for a card. -->
+    <color name="chassis_card_ripple_background">#8F000000</color>
 </resources>
diff --git a/car-chassis-lib/res/values-port/dimens.xml b/car-chassis-lib/res/values-port/dimens.xml
index d91a195..543e63b 100644
--- a/car-chassis-lib/res/values-port/dimens.xml
+++ b/car-chassis-lib/res/values-port/dimens.xml
@@ -15,5 +15,5 @@
   limitations under the License.
   -->
 <resources>
-    <dimen name="chassis_toolbar_second_row_height">@*android:dimen/car_app_bar_height</dimen>
+    <dimen name="chassis_toolbar_second_row_height">@dimen/chassis_app_bar_height</dimen>
 </resources>
diff --git a/car-chassis-lib/res/values-w1280dp/dimens.xml b/car-chassis-lib/res/values-w1280dp/dimens.xml
new file mode 100644
index 0000000..2794db3
--- /dev/null
+++ b/car-chassis-lib/res/values-w1280dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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>
+    <!-- Margin -->
+    <dimen name="chassis_margin">148dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/res/values-w1920dp/dimens.xml b/car-chassis-lib/res/values-w1920dp/dimens.xml
new file mode 100644
index 0000000..dc68fdf
--- /dev/null
+++ b/car-chassis-lib/res/values-w1920dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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>
+    <!-- Margin -->
+    <dimen name="chassis_margin">192dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/res/values-w690dp/dimens.xml b/car-chassis-lib/res/values-w690dp/dimens.xml
new file mode 100644
index 0000000..dbfb227
--- /dev/null
+++ b/car-chassis-lib/res/values-w690dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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>
+    <!-- Margin -->
+    <dimen name="chassis_margin">112dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/res/values/attrs.xml b/car-chassis-lib/res/values/attrs.xml
index 5a49a20..4caf70b 100644
--- a/car-chassis-lib/res/values/attrs.xml
+++ b/car-chassis-lib/res/values/attrs.xml
@@ -15,18 +15,51 @@
 -->
 <resources>
     <declare-styleable name="ChassisToolbar">
-        <!-- The title of the toolbar, only displayed in certain conditions -->
+        <!-- Title of the toolbar, only displayed in certain conditions -->
         <attr name="title" format="string"/>
-        <!-- The logo drawable for the toolbar. Appears when there's no back/close button shown -->
+        <!-- Logo drawable for the toolbar. Appears when there's no back/close button shown -->
         <attr name="logo" format="reference"/>
-        <!-- The hint for the search bar in the toolbar -->
+        <!-- Hint for the search bar in the toolbar -->
         <attr name="searchHint" format="string"/>
-        <!-- The buttons to display, as an array of layout ids. Use @layout/chassis_toolbar_search_button and @layout/chassis_toolbar_settings_button for the search and settings buttons -->
+        <!-- Buttons to display, as an array of layout ids. Use @layout/chassis_toolbar_search_button and @layout/chassis_toolbar_settings_button for the search and settings buttons -->
         <attr name="buttons" format="reference"/>
         <!-- Whether or not to show the custom buttons while searching. If using chassis_toolbar_search_button, or any other layout with a view with the id of "search", it will always be hidden. -->
         <attr name="showButtonsWhileSearching" format="boolean"/>
+        <!-- Initial state of the toolbar. See the Toolbar.State enum for more information -->
+        <attr name="state" format="enum">
+            <enum name="home" value="0"/>
+            <enum name="subpage" value="1"/>
+            <enum name="subpage_custom" value="2"/>
+            <enum name="search" value="3"/>
+        </attr>
+        <!-- Whether or not the toolbar should have a background. Default true. -->
+        <attr name="showBackground" format="boolean"/>
     </declare-styleable>
 
     <!-- Theme attribute to specifying a default style for all chassisToolbars -->
     <attr name="chassisToolbarStyle" format="reference"/>
+
+    <declare-styleable name="PagedRecyclerView">
+        <!-- Whether to enable the chassis_pagedrecyclerview_divider for linear layout or not. -->
+        <attr name="enableDivider" format="boolean" />
+        <!-- Top offset for paged recycler view. -->
+        <attr name="startOffset" format="integer" />
+        <!-- Bottom offset for paged recycler view for linear layout. -->
+        <attr name="endOffset" format="integer" />
+
+        <!-- Number of columns in a grid layout. -->
+        <attr name="numOfColumns" format="integer" />
+
+        <!-- Paged recycler view layout. -->
+        <attr name="layoutStyle" format="enum">
+            <!-- linear layout -->
+            <enum name="linear" value="0" />
+            <!-- grid layout -->
+            <enum name="grid" value="1" />
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="PagedRecyclerViewTheme">
+        <attr name="pagedRecyclerViewStyle" format="reference" />
+    </declare-styleable>
 </resources>
diff --git a/car-chassis-lib/res/values/bools.xml b/car-chassis-lib/res/values/bools.xml
new file mode 100644
index 0000000..ff1f439
--- /dev/null
+++ b/car-chassis-lib/res/values/bools.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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>
+
+    <!-- Whether to display the Scroll Bar or not. Defaults to true. If this is set to false,
+         the PagedRecyclerView will behave exactly like the RecyclerView. -->
+    <bool name="chassis_scrollbar_enable">true</bool>
+
+    <!-- Whether to place the scrollbar z-index above the recycler view. Defaults to
+         true. -->
+    <bool name="chassis_scrollbar_above_recycler_view">true</bool>
+</resources>
diff --git a/car-chassis-lib/res/values/colors.xml b/car-chassis-lib/res/values/colors.xml
index 7c06fa2..caf7266 100644
--- a/car-chassis-lib/res/values/colors.xml
+++ b/car-chassis-lib/res/values/colors.xml
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2019 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.
@@ -14,16 +14,30 @@
      limitations under the License.
 -->
 <resources>
-    <color name="primary_text_color">#FFFFFFFF</color>
-    <color name="secondary_text_color">#90FFFFFF</color>
-    <color name="search_hint_text_color">#33FFFFFF</color>
+    <!-- General -->
 
-    <color name="toolbar_background_color">#E0000000</color>
+    <!-- Main text color (titles, text body, etc.) -->
+    <color name="chassis_primary_text_color">#FFFFFFFF</color>
+    <!-- Text color used in subtitles or other secondary text blocks -->
+    <color name="chassis_secondary_text_color">#90FFFFFF</color>
+    <!-- The ripple color for a card. -->
+    <color name="chassis_card_ripple_background">#27ffffff</color>
 
-    <color name="chassis_tab_selected_color">@color/chassis_tab_selected_color_light</color>
-    <color name="chassis_tab_selected_color_dark">#E2FFFFFF</color>
-    <color name="chassis_tab_selected_color_light">#FFFFFFFF</color>
-    <color name="chassis_tab_unselected_color">@color/chassis_tab_unselected_color_light</color>
-    <color name="chassis_tab_unselected_color_dark">#80FFFFFF</color>
-    <color name="chassis_tab_unselected_color_light">#90FFFFFF</color>
+    <!-- Tabs -->
+
+    <!-- Selected colors -->
+    <color name="chassis_tab_selected_color">@color/chassis_primary_text_color</color>
+    <!-- Normal colors -->
+    <color name="chassis_tab_unselected_color">@color/chassis_secondary_text_color</color>
+
+    <!-- Toolbar -->
+
+    <!-- Text color applied to the hint displayed inside the search box -->
+    <color name="chassis_toolbar_search_hint_text_color">#33FFFFFF</color>
+    <!-- Toolbar background color -->
+    <color name="chassis_toolbar_background_color">#E0000000</color>
+
+    <!--  Paged Recycler View  -->
+    <!-- The color of the scroll bar indicator in the PagedListView. -->
+    <color name="chassis_scrollbar_thumb">#99ffffff</color>
 </resources>
diff --git a/car-chassis-lib/res/values/config.xml b/car-chassis-lib/res/values/config.xml
new file mode 100644
index 0000000..8a0875f
--- /dev/null
+++ b/car-chassis-lib/res/values/config.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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>
+    <!--
+    Configuration for a default scrollbar for the PagedRecyclerView. This component must inherit
+    abstract class ScrollBar. If the ScrollBar is enabled, the component will be initialized from
+    PagedRecyclerView#createScrollBarFromConfig().
+    -->
+    <string name="chassis_scrollbar_component" translatable="false">
+        com.google.android.apps.automotive.chassis.libraries.CarScrollBar
+    </string>
+
+    <!--
+    Whether to include a gutter to the start, end or both sides of the list view items.
+    The gutter width will be the width of the scrollbar, and by default will be set to
+    both. Values are defined as follows:
+      none = 0
+      start = 1
+      end = 2
+      both = 3
+    -->
+    <integer name="chassis_scrollbar_gutter" translatable="false">1</integer>
+
+    <!--
+    Position of the scrollbar. Default to left. Values are defined as follows:
+      start = 0
+      end = 1
+    -->
+    <integer name="chassis_scrollbar_position" translatable="false">0</integer>
+
+    <!-- Width of the scrollbar container. -->
+    <dimen name="chassis_scrollbar_container_width" translatable="false">@*android:dimen/car_margin</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/res/values/dimens.xml b/car-chassis-lib/res/values/dimens.xml
index 6bc61dd..5565bfb 100644
--- a/car-chassis-lib/res/values/dimens.xml
+++ b/car-chassis-lib/res/values/dimens.xml
@@ -14,22 +14,68 @@
  limitations under the License.
 -->
 <resources>
-    <dimen name="touch_target_size">@*android:dimen/car_touch_target_size</dimen>
-    <dimen name="primary_icon_size">@*android:dimen/car_primary_icon_size</dimen>
-    <item name="letter_spacing_body1" format="float" type="dimen">0.0</item>
-    <item name="letter_spacing_body3" format="float" type="dimen">0.0</item>
+    <!-- General resources -->
+
+    <dimen name="chassis_touch_target_width">76dp</dimen>
+    <dimen name="chassis_touch_target_height">76dp</dimen>
+    <dimen name="chassis_primary_icon_size">44dp</dimen>
+    <item name="chassis_letter_spacing_body1" format="float" type="dimen">0.0</item>
+    <item name="chassis_letter_spacing_body3" format="float" type="dimen">0.0</item>
+
+    <!-- Application Bar -->
+    <dimen name="chassis_app_bar_height">80dp</dimen>
+
+    <!-- Margin -->
+    <dimen name="chassis_margin">20dp</dimen>
+
+    <!-- Paddings -->
+    <dimen name="chassis_padding_0">4dp</dimen>
+    <dimen name="chassis_padding_1">8dp</dimen>
+    <dimen name="chassis_padding_2">16dp</dimen>
+    <dimen name="chassis_padding_3">24dp</dimen>
+    <dimen name="chassis_padding_4">32dp</dimen>
+    <dimen name="chassis_padding_5">64dp</dimen>
+    <dimen name="chassis_padding_6">96dp</dimen>
 
     <!-- Tabs -->
-    <dimen name="chassis_tab_width">135dp</dimen>
+
+    <!-- Exact size of the tab textbox. Use @dimen/wrap_content if this must be flexible -->
+    <dimen name="chassis_tab_text_width">135dp</dimen>
+    <!-- Horizontal padding between tabs -->
     <dimen name="chassis_tab_padding_x">12dp</dimen>
-    <dimen name="chassis_tab_icon_size">36dp</dimen>
+    <!-- Tab icon width (if icons are enabled) -->
+    <dimen name="chassis_tab_icon_width">36dp</dimen>
+    <!-- Tab icon height (if icons are enabled) -->
+    <dimen name="chassis_tab_icon_height">36dp</dimen>
 
     <!-- Car toolbar -->
-    <dimen name="chassis_toolbar_view_nav_button_width">@*android:dimen/car_margin</dimen>
-    <dimen name="chassis_toolbar_first_row_height">@*android:dimen/car_app_bar_height</dimen>
+    <dimen name="chassis_toolbar_view_nav_button_width">@dimen/chassis_margin</dimen>
+    <dimen name="chassis_toolbar_first_row_height">@dimen/chassis_app_bar_height</dimen>
     <dimen name="chassis_toolbar_second_row_height">0dp</dimen>
-    <dimen name="chassis_toolbar_view_icon_size">@*android:dimen/car_primary_icon_size</dimen>
-    <dimen name="chassis_toolbar_view_title_margin_start">@*android:dimen/car_padding_2</dimen>
-    <dimen name="chassis_toolbar_custom_button_margin">@*android:dimen/car_padding_2</dimen>
+    <dimen name="chassis_toolbar_view_icon_size">@dimen/chassis_primary_icon_size</dimen>
+    <dimen name="chassis_toolbar_view_title_margin_start">@dimen/chassis_padding_2</dimen>
+    <dimen name="chassis_toolbar_custom_button_margin">@dimen/chassis_padding_2</dimen>
     <dimen name="chassis_toolbar_button_background_radius">48dp</dimen>
+
+    <!-- Internal artifacts. Do not overlay -->
+    <item name="wrap_content" format="integer" type="dimen">-2</item>
+
+    <!-- Default Scroll Bar for PagedRecyclerView -->
+    <dimen name="chassis_scrollbar_button_size">76dp</dimen>
+    <dimen name="chassis_scrollbar_thumb_width">6dp</dimen>
+    <dimen name="chassis_scrollbar_separator_margin">16dp</dimen>
+    <dimen name="chassis_scrollbar_margin">20dp</dimen>
+    <dimen name="chassis_scrollbar_thumb_radius">100dp</dimen>
+
+    <item name="chassis_button_disabled_alpha" format="float" type="dimen">0.2</item>
+    <item name="chassis_scroller_milliseconds_per_inch" format="float" type="dimen">150</item>
+    <item name="chassis_scroller_deceleration_time_divisor" format="float" type="dimen">0.45</item>
+    <item name="chassis_scroller_interpolator_factor" format="float" type="dimen">1.8</item>
+
+    <item name="chassis_scrollbar_milliseconds_per_inch" format="float" type="dimen">150.0</item>
+    <item name="chassis_scrollbar_deceleration_times_divisor" format="float" type="dimen">0.45</item>
+    <item name="chassis_scrollbar_decelerate_interpolator_factor" format="float" type="dimen">1.8</item>
+
+    <dimen name="chassis_scrollbar_padding_start">0dp</dimen>
+    <dimen name="chassis_scrollbar_padding_end">0dp</dimen>
 </resources>
diff --git a/car-chassis-lib/res/values/integers.xml b/car-chassis-lib/res/values/integers.xml
new file mode 100644
index 0000000..083fa2e
--- /dev/null
+++ b/car-chassis-lib/res/values/integers.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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>
+
+    <!-- Default max string length -->
+    <integer name="chassis_default_max_string_length">120</integer>
+
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/res/values/strings.xml b/car-chassis-lib/res/values/strings.xml
index b93e4a1..38ea967 100644
--- a/car-chassis-lib/res/values/strings.xml
+++ b/car-chassis-lib/res/values/strings.xml
@@ -15,5 +15,11 @@
 -->
 <resources>
     <!-- Search hint, displayed inside the search box [CHAR LIMIT=50] -->
-    <string name="chassis_default_search_hint">Search&#8230;</string>
+    <string name="chassis_toolbar_default_search_hint">Search&#8230;</string>
+    <!-- CarUxRestrictions Utility -->
+    <string name="chassis_ellipsis" translatable="false">&#8230;</string>
+    <!-- Content description for paged recycler view scroll bar down arrow [CHAR LIMIT=30] -->
+    <string name="chassis_scrollbar_page_down_button">Scroll down</string>
+    <!-- Content description for paged recycler view scroll bar up arrow [CHAR LIMIT=30] -->
+    <string name="chassis_scrollbar_page_up_button">Scroll up</string>
 </resources>
diff --git a/car-chassis-lib/res/values/styles.xml b/car-chassis-lib/res/values/styles.xml
index 5214d2d..6d5308f 100644
--- a/car-chassis-lib/res/values/styles.xml
+++ b/car-chassis-lib/res/values/styles.xml
@@ -19,15 +19,15 @@
     <style name="ChassisTabItemText">
         <item name="android:textAppearance">@style/TextAppearance.Body3</item>
         <item name="android:textColor">@color/chassis_tab_item_selector</item>
-        <item name="android:layout_width">@dimen/chassis_tab_width</item>
+        <item name="android:layout_width">@dimen/chassis_tab_text_width</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:singleLine">true</item>
         <item name="android:gravity">center</item>
     </style>
 
     <style name="ChassisTabItemIcon">
-        <item name="android:layout_width">@dimen/chassis_tab_icon_size</item>
-        <item name="android:layout_height">@dimen/chassis_tab_icon_size</item>
+        <item name="android:layout_width">@dimen/chassis_tab_icon_width</item>
+        <item name="android:layout_height">@dimen/chassis_tab_icon_height</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:tint">@color/chassis_tab_item_selector</item>
         <item name="android:tintMode">src_in</item>
@@ -67,7 +67,8 @@
         <item name="android:layout_width">@dimen/chassis_toolbar_view_icon_size</item>
         <item name="android:layout_height">@dimen/chassis_toolbar_view_icon_size</item>
         <item name="android:layout_gravity">center</item>
-        <item name="android:src">@drawable/ic_arrow_back</item>
+        <item name="android:tint">@color/chassis_primary_text_color</item>
+        <item name="android:src">@drawable/chassis_icon_arrow_back</item>
         <item name="android:scaleType">fitXY</item>
     </style>
 
@@ -80,17 +81,21 @@
 
     <style name="TextAppearance">
         <item name="android:fontFamily">roboto-regular</item>
-        <item name="android:textColor">@color/primary_text_color</item>
+        <item name="android:textColor">@color/chassis_primary_text_color</item>
     </style>
 
     <style name="TextAppearance.Body1" parent="TextAppearance">
         <item name="android:textSize">32sp</item>
-        <item name="android:letterSpacing">@dimen/letter_spacing_body1</item>
+        <item name="android:letterSpacing">@dimen/chassis_letter_spacing_body1</item>
     </style>
 
     <style name="TextAppearance.Body3" parent="TextAppearance">
         <item name="android:textSize">24sp</item>
-        <item name="android:letterSpacing">@dimen/letter_spacing_body3</item>
+        <item name="android:letterSpacing">@dimen/chassis_letter_spacing_body3</item>
     </style>
 
+    <style name="PagedRecyclerView">
+    </style>
+    <style name="PagedRecyclerView.NestedRecyclerView">
+    </style>
 </resources>
diff --git a/car-chassis-lib/res/values/themes.xml b/car-chassis-lib/res/values/themes.xml
new file mode 100644
index 0000000..70ea7ae
--- /dev/null
+++ b/car-chassis-lib/res/values/themes.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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">
+    <!-- Base application theme. -->
+    <style name="ChassisTheme" parent="android:Theme.DeviceDefault">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/car-chassis-lib/settings.gradle b/car-chassis-lib/settings.gradle
new file mode 100644
index 0000000..321a5db
--- /dev/null
+++ b/car-chassis-lib/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+include ':PaintBooth'
+project(':PaintBooth').projectDir = new File('./tests/paintbooth')
+rootProject.name='Chassis'
diff --git a/car-chassis-lib/src/com/android/car/chassis/Toolbar.java b/car-chassis-lib/src/com/android/car/chassis/Toolbar.java
index eb5ef7f..0cb2373 100644
--- a/car-chassis-lib/src/com/android/car/chassis/Toolbar.java
+++ b/car-chassis-lib/src/com/android/car/chassis/Toolbar.java
@@ -17,7 +17,9 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -47,6 +49,8 @@
  */
 public class Toolbar extends FrameLayout {
 
+    private static final String TAG = "ChassisToolbar";
+
     /** Enum of states the toolbar can be in. Controls what elements of the toolbar are displayed */
     public enum State {
         /**
@@ -127,7 +131,7 @@
         mTitle.setText(a.getString(R.styleable.ChassisToolbar_title));
         setLogo(a.getResourceId(R.styleable.ChassisToolbar_logo, 0));
         setButtons(a.getResourceId(R.styleable.ChassisToolbar_buttons, 0));
-        setBackground(context.getDrawable(R.color.toolbar_background_color));
+        setBackgroundShown(a.getBoolean(R.styleable.ChassisToolbar_showBackground, true));
         mShowButtonsWhileSearching = a.getBoolean(
                 R.styleable.ChassisToolbar_showButtonsWhileSearching, false);
         String searchHint = a.getString(R.styleable.ChassisToolbar_searchHint);
@@ -135,6 +139,26 @@
             setSearchHint(searchHint);
         }
 
+        switch (a.getInt(R.styleable.ChassisToolbar_state, 0)) {
+            case 0:
+                setState(State.HOME);
+                break;
+            case 1:
+                setState(State.SUBPAGE);
+                break;
+            case 2:
+                setState(State.SUBPAGE_CUSTOM);
+                break;
+            case 3:
+                setState(State.SEARCH);
+                break;
+            default:
+                if (Log.isLoggable(TAG, Log.WARN)) {
+                    Log.w(TAG, "Unknown initial state");
+                }
+                break;
+        }
+
         a.recycle();
 
         mTabLayout.addListener(new TabLayout.Listener() {
@@ -223,6 +247,27 @@
     }
 
     /**
+     * setBackground is disallowed, to prevent apps from deviating from the intended style too much.
+     */
+    @Override
+    public void setBackground(Drawable d) {
+        throw new UnsupportedOperationException(
+                "You can not change the background of a chassis toolbar, use "
+                + "setBackgroundShown(boolean) or an RRO instead.");
+    }
+
+    /**
+     * Show/hide the background. When hidden, the toolbar is completely transparent.
+     */
+    public void setBackgroundShown(boolean shown) {
+        if (shown) {
+            super.setBackground(getContext().getDrawable(R.color.chassis_toolbar_background_color));
+        } else {
+            super.setBackground(null);
+        }
+    }
+
+    /**
      * Sets the buttons to be shown. Click events for these buttons will be received in
      * {@link Listener#onCustomButtonPressed(View)}.
      *
@@ -336,7 +381,7 @@
 
         View.OnClickListener backClickListener = (v) -> forEachListener(Listener::onBack);
         mNavIcon.setVisibility(state != State.HOME ? VISIBLE : INVISIBLE);
-        mNavIcon.setImageResource(state != State.HOME ? R.drawable.ic_arrow_back : 0);
+        mNavIcon.setImageResource(state != State.HOME ? R.drawable.chassis_icon_arrow_back : 0);
         mLogo.setVisibility(state == State.HOME && mHasLogo ? VISIBLE : INVISIBLE);
         mNavIconContainer.setVisibility(state != State.HOME || mHasLogo ? VISIBLE : GONE);
         mNavIconContainer.setClickable(state != State.HOME);
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarScrollBar.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarScrollBar.java
new file mode 100644
index 0000000..0ec40d4
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarScrollBar.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.annotation.IntRange;
+import androidx.recyclerview.widget.OrientationHelper;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.chassis.R;
+import com.android.car.chassis.pagedrecyclerview.PagedRecyclerView.ScrollBarPosition;
+
+/**
+ * The default scroll bar widget for the {@link PagedRecyclerView}.
+ *
+ * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic has
+ * been ported from the PLV with minor updates.
+ */
+class CarScrollBar implements ScrollBar {
+    private float mButtonDisabledAlpha;
+    private static final String TAG = "CarScrollBar";
+    private PagedSnapHelper mSnapHelper;
+
+    private ImageView mUpButton;
+    private View mScrollView;
+    private View mScrollThumb;
+    private ImageView mDownButton;
+    private int mPaddingStart;
+    private int mPaddingEnd;
+
+    private int mSeparatingMargin;
+
+    private RecyclerView mRecyclerView;
+
+    /** The amount of space that the scroll thumb is allowed to roam over. */
+    private int mScrollThumbTrackHeight;
+
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+
+    private final int mRowsPerPage = -1;
+    private final Handler mHandler = new Handler();
+
+    private OrientationHelper mOrientationHelper;
+
+    @Override
+    public void initialize(
+            RecyclerView rv,
+            int scrollBarContainerWidth,
+            @ScrollBarPosition int scrollBarPosition,
+            boolean scrollBarAboveRecyclerView) {
+
+        this.mRecyclerView = rv;
+
+        LayoutInflater inflater =
+                (LayoutInflater) rv.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        FrameLayout parent = (FrameLayout) getRecyclerView().getParent();
+
+        mScrollView = inflater.inflate(R.layout.chassis_pagedrecyclerview_scrollbar, parent, false);
+        mScrollView.setLayoutParams(
+                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
+
+        Resources res = rv.getContext().getResources();
+        mButtonDisabledAlpha = res.getFloat(R.dimen.chassis_button_disabled_alpha);
+
+        if (scrollBarAboveRecyclerView) {
+            parent.addView(mScrollView);
+        } else {
+            parent.addView(mScrollView, /* index= */ 0);
+        }
+
+        setScrollBarContainerWidth(scrollBarContainerWidth);
+        setScrollBarPosition(scrollBarPosition);
+
+        getRecyclerView().addOnScrollListener(mRecyclerViewOnScrollListener);
+        getRecyclerView().getRecycledViewPool().setMaxRecycledViews(0, 12);
+
+        mSeparatingMargin = res.getDimensionPixelSize(R.dimen.chassis_scrollbar_separator_margin);
+
+        mUpButton = mScrollView.findViewById(R.id.page_up);
+        PaginateButtonClickListener upButtonClickListener =
+                new PaginateButtonClickListener(PaginationListener.PAGE_UP);
+        mUpButton.setOnClickListener(upButtonClickListener);
+
+        mDownButton = mScrollView.findViewById(R.id.page_down);
+        PaginateButtonClickListener downButtonClickListener =
+                new PaginateButtonClickListener(PaginationListener.PAGE_DOWN);
+        mDownButton.setOnClickListener(downButtonClickListener);
+
+        mScrollThumb = mScrollView.findViewById(R.id.scrollbar_thumb);
+
+        mSnapHelper = new PagedSnapHelper(rv.getContext());
+        getRecyclerView().setOnFlingListener(null);
+        mSnapHelper.attachToRecyclerView(getRecyclerView());
+
+        mScrollView.addOnLayoutChangeListener(
+                (View v,
+                        int left,
+                        int top,
+                        int right,
+                        int bottom,
+                        int oldLeft,
+                        int oldTop,
+                        int oldRight,
+                        int oldBottom) -> {
+                    int width = right - left;
+
+                    OrientationHelper orientationHelper =
+                            getOrientationHelper(getRecyclerView().getLayoutManager());
+
+                    // This value will keep track of the top of the current view being laid out.
+                    int layoutTop = orientationHelper.getStartAfterPadding() + mPaddingStart;
+
+                    // Lay out the up button at the top of the view.
+                    layoutViewCenteredFromTop(mUpButton, layoutTop, width);
+                    layoutTop = mUpButton.getBottom();
+
+                    // Lay out the scroll thumb
+                    layoutTop += mSeparatingMargin;
+                    layoutViewCenteredFromTop(mScrollThumb, layoutTop, width);
+
+                    // Lay out the bottom button at the bottom of the view.
+                    int downBottom = orientationHelper.getEndAfterPadding() - mPaddingEnd;
+                    layoutViewCenteredFromBottom(mDownButton, downBottom, width);
+
+                    mHandler.post(this::calculateScrollThumbTrackHeight);
+                    mHandler.post(() -> updatePaginationButtons(/* animate= */ false));
+                });
+    }
+
+    public RecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    @Override
+    public void requestLayout() {
+        mScrollView.requestLayout();
+    }
+
+    /**
+     * Sets the width of the container that holds the scrollbar. The scrollbar will be centered
+     * within
+     * this width.
+     *
+     * @param width The width of the scrollbar container.
+     */
+    private void setScrollBarContainerWidth(int width) {
+        ViewGroup.LayoutParams layoutParams = mScrollView.getLayoutParams();
+        layoutParams.width = width;
+        mScrollView.requestLayout();
+    }
+
+    @Override
+    public void setPadding(int paddingStart, int paddingEnd) {
+        this.mPaddingStart = paddingStart;
+        this.mPaddingEnd = paddingEnd;
+        requestLayout();
+    }
+
+    /**
+     * Sets the position of the scrollbar.
+     *
+     * @param position Enum value of the scrollbar position. 0 for Start and 1 for end.
+     */
+    private void setScrollBarPosition(@ScrollBarPosition int position) {
+        FrameLayout.LayoutParams layoutParams =
+                (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
+        if (position == ScrollBarPosition.START) {
+            layoutParams.gravity = Gravity.LEFT;
+        } else {
+            layoutParams.gravity = Gravity.RIGHT;
+        }
+
+        mScrollView.requestLayout();
+    }
+
+    /**
+     * Sets whether or not the up button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the up button is enabled.
+     */
+    private void setUpEnabled(boolean enabled) {
+        mUpButton.setEnabled(enabled);
+        mUpButton.setAlpha(enabled ? 1f : mButtonDisabledAlpha);
+    }
+
+    /**
+     * Sets whether or not the down button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the down button is enabled.
+     */
+    private void setDownEnabled(boolean enabled) {
+        mDownButton.setEnabled(enabled);
+        mDownButton.setAlpha(enabled ? 1f : mButtonDisabledAlpha);
+    }
+
+    /**
+     * Returns whether or not the down button on the scroll bar is clickable.
+     *
+     * @return {@code true} if the down button is enabled. {@code false} otherwise.
+     */
+    private boolean isDownEnabled() {
+        return mDownButton.isEnabled();
+    }
+
+    /** Listener for when the list should paginate. */
+    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);
+    }
+
+    /**
+     * Calculate the amount of space that the scroll bar thumb is allowed to roam. The thumb is
+     * allowed to take up the space between the down bottom and the up or alpha jump button,
+     * depending
+     * on if the latter is visible.
+     */
+    private void calculateScrollThumbTrackHeight() {
+        // Subtracting (2 * mSeparatingMargin) for the top/bottom margin above and below the
+        // scroll bar thumb.
+        mScrollThumbTrackHeight = mDownButton.getTop() - (2 * mSeparatingMargin);
+
+        // If there's an alpha jump button, then the thumb is laid out starting from below that.
+        mScrollThumbTrackHeight -= mUpButton.getBottom();
+    }
+
+    /**
+     * Lays out the given View starting from the given {@code top} value downwards and centered
+     * within
+     * the given {@code availableWidth}.
+     *
+     * @param view The view to lay out.
+     * @param top The top value to start laying out from. This value will be the resulting top value
+     * of the view.
+     * @param availableWidth The width in which to center the given view.
+     */
+    private static void layoutViewCenteredFromTop(View view, int top, int availableWidth) {
+        int viewWidth = view.getMeasuredWidth();
+        int viewLeft = (availableWidth - viewWidth) / 2;
+        view.layout(viewLeft, top, viewLeft + viewWidth, top + view.getMeasuredHeight());
+    }
+
+    /**
+     * Lays out the given View starting from the given {@code bottom} value upwards and centered
+     * within the given {@code availableSpace}.
+     *
+     * @param view The view to lay out.
+     * @param bottom The bottom value to start laying out from. This value will be the resulting
+     * bottom value of the view.
+     * @param availableWidth The width in which to center the given view.
+     */
+    private static void layoutViewCenteredFromBottom(View view, int bottom, int availableWidth) {
+        int viewWidth = view.getMeasuredWidth();
+        int viewLeft = (availableWidth - viewWidth) / 2;
+        view.layout(viewLeft, bottom - view.getMeasuredHeight(), viewLeft + viewWidth, bottom);
+    }
+
+    /**
+     * Sets the range, offset and extent of the scroll bar. The range represents the size of a
+     * container for the scrollbar thumb; offset is the distance from the start of the container to
+     * where the thumb should be; and finally, extent is the size of the thumb.
+     *
+     * <p>These values can be expressed in arbitrary units, so long as they share the same units.
+     * The
+     * values should also be positive.
+     *
+     * @param range The range of the scrollbar's thumb
+     * @param offset The offset of the scrollbar's thumb
+     * @param extent The extent of the scrollbar's thumb
+     * @param animate Whether or not the thumb should animate from its current position to the
+     * position specified by the given range, offset and extent.
+     */
+    private void setParameters(
+            @IntRange(from = 0) int range,
+            @IntRange(from = 0) int offset,
+            @IntRange(from = 0) int extent,
+            boolean animate) {
+        // Not laid out yet, so values cannot be calculated.
+        if (!mScrollView.isLaidOut()) {
+            return;
+        }
+
+        // If the scroll bars aren't visible, then no need to update.
+        if (mScrollView.getVisibility() == View.GONE || range == 0) {
+            return;
+        }
+
+        int thumbLength = calculateScrollThumbLength(range, extent);
+        int thumbOffset = calculateScrollThumbOffset(range, offset, thumbLength);
+
+        // Sets the size of the thumb and request a redraw if needed.
+        ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
+
+        if (lp.height != thumbLength) {
+            lp.height = thumbLength;
+            mScrollThumb.requestLayout();
+        }
+
+        moveY(mScrollThumb, thumbOffset, animate);
+    }
+
+    /**
+     * Calculates and returns how big the scroll bar thumb should be based on the given range and
+     * extent.
+     *
+     * @param range The total amount of space the scroll bar is allowed to roam over.
+     * @param extent The amount of space that the scroll bar takes up relative to the range.
+     * @return The height of the scroll bar thumb in pixels.
+     */
+    private int calculateScrollThumbLength(int range, int extent) {
+        // Scale the length by the available space that the thumb can fill.
+        return Math.round(((float) extent / range) * mScrollThumbTrackHeight);
+    }
+
+    /**
+     * Calculates and returns how much the scroll thumb should be offset from the top of where it
+     * has
+     * been laid out.
+     *
+     * @param range The total amount of space the scroll bar is allowed to roam over.
+     * @param offset The amount the scroll bar should be offset, expressed in the same units as the
+     * given range.
+     * @param thumbLength The current length of the thumb in pixels.
+     * @return The amount the thumb should be offset in pixels.
+     */
+    private int calculateScrollThumbOffset(int range, int offset, int thumbLength) {
+        // Ensure that if the user has reached the bottom of the list, then the scroll bar is
+        // aligned to the bottom as well. Otherwise, scale the offset appropriately.
+        // This offset will be a value relative to the parent of this scrollbar, so start by where
+        // the top of mScrollThumb is.
+        return mScrollThumb.getTop()
+                + (isDownEnabled()
+                ? Math.round(((float) offset / range) * mScrollThumbTrackHeight)
+                : mScrollThumbTrackHeight - thumbLength);
+    }
+
+    /** 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();
+    }
+
+    private class PaginateButtonClickListener implements View.OnClickListener {
+        private final int mPaginateDirection;
+        private PaginationListener mPaginationListener;
+
+        PaginateButtonClickListener(int paginateDirection) {
+            this.mPaginateDirection = paginateDirection;
+        }
+
+        @Override
+        public void onClick(View v) {
+            if (mPaginationListener != null) {
+                mPaginationListener.onPaginate(mPaginateDirection);
+            }
+            if (mPaginateDirection == PaginationListener.PAGE_DOWN) {
+                pageDown();
+            } else if (mPaginateDirection == PaginationListener.PAGE_UP) {
+                pageUp();
+            }
+        }
+    }
+
+    private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                    updatePaginationButtons(false);
+                }
+            };
+
+    /** Returns the page the given position is on, starting with page 0. */
+    int getPage(int position) {
+        if (mRowsPerPage == -1) {
+            return -1;
+        }
+        if (mRowsPerPage == 0) {
+            return 0;
+        }
+        return position / mRowsPerPage;
+    }
+
+    private OrientationHelper getOrientationHelper(RecyclerView.LayoutManager layoutManager) {
+        if (mOrientationHelper == null || mOrientationHelper.getLayoutManager() != layoutManager) {
+            // PagedRecyclerView is assumed to be a list that always vertically scrolls.
+            mOrientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
+        }
+        return mOrientationHelper;
+    }
+
+    /**
+     * Scrolls the contents of the RecyclerView up a page. A page is defined as the height of the
+     * {@code PagedRecyclerView}.
+     *
+     * <p>The resulting first item in the list will be snapped to so that it is completely visible.
+     * If
+     * this is not possible due to the first item being taller than the containing {@code
+     * PagedRecyclerView}, then the snapping will not occur.
+     */
+    void pageUp() {
+        int currentOffset = getRecyclerView().computeVerticalScrollOffset();
+        if (getRecyclerView().getLayoutManager() == null
+                || getRecyclerView().getChildCount() == 0
+                || currentOffset == 0) {
+            return;
+        }
+
+        // Use OrientationHelper to calculate scroll distance in order to match snapping behavior.
+        OrientationHelper orientationHelper =
+                getOrientationHelper(getRecyclerView().getLayoutManager());
+        int screenSize = orientationHelper.getTotalSpace();
+
+        int scrollDistance = screenSize;
+        // The iteration order matters. In case where there are 2 items longer than screen size, we
+        // want to focus on upcoming view.
+        for (int i = 0; i < getRecyclerView().getChildCount(); i++) {
+            /*
+             * We treat child View longer than screen size differently:
+             * 1) When it enters screen, next pageUp will align its bottom with parent bottom;
+             * 2) When it leaves screen, next pageUp will align its top with parent top.
+             */
+            View child = getRecyclerView().getChildAt(i);
+            if (child.getHeight() > screenSize) {
+                if (orientationHelper.getDecoratedEnd(child) < screenSize) {
+                    // Child view bottom is entering screen. Align its bottom with parent bottom.
+                    scrollDistance = screenSize - orientationHelper.getDecoratedEnd(child);
+                } else if (-screenSize < orientationHelper.getDecoratedStart(child)
+                        && orientationHelper.getDecoratedStart(child) < 0) {
+                    // Child view top is about to enter screen - its distance to parent top
+                    // is less than a full scroll. Align child top with parent top.
+                    scrollDistance = Math.abs(orientationHelper.getDecoratedStart(child));
+                }
+                // There can be two items that are longer than the screen. We stop at the first one.
+                // This is affected by the iteration order.
+                break;
+            }
+        }
+        // Distance should always be positive. Negate its value to scroll up.
+        getRecyclerView().smoothScrollBy(0, -scrollDistance);
+    }
+
+    /**
+     * Scrolls the contents of the RecyclerView down a page. A page is defined as the height of the
+     * {@code PagedRecyclerView}.
+     *
+     * <p>This method will attempt to bring the last item in the list as the first item. If the
+     * current first item in the list is taller than the {@code PagedRecyclerView}, then it will be
+     * scrolled the length of a page, but not snapped to.
+     */
+    void pageDown() {
+        if (getRecyclerView().getLayoutManager() == null
+                || getRecyclerView().getChildCount() == 0) {
+            return;
+        }
+
+        OrientationHelper orientationHelper =
+                getOrientationHelper(getRecyclerView().getLayoutManager());
+        int screenSize = orientationHelper.getTotalSpace();
+        int scrollDistance = screenSize;
+
+        // If the last item is partially visible, page down should bring it to the top.
+        View lastChild = getRecyclerView().getChildAt(getRecyclerView().getChildCount() - 1);
+        if (getRecyclerView()
+                .getLayoutManager()
+                .isViewPartiallyVisible(
+                        lastChild, /* completelyVisible= */ false, /* acceptEndPointInclusion= */
+                        false)) {
+            scrollDistance = orientationHelper.getDecoratedStart(lastChild);
+            if (scrollDistance < 0) {
+                // Scroll value can be negative if the child is longer than the screen size and the
+                // visible area of the screen does not show the start of the child.
+                // Scroll to the next screen if the start value is negative
+                scrollDistance = screenSize;
+            }
+        }
+
+        // The iteration order matters. In case where there are 2 items longer than screen size, we
+        // want to focus on upcoming view (the one at the bottom of screen).
+        for (int i = getRecyclerView().getChildCount() - 1; i >= 0; i--) {
+            /* We treat child View longer than screen size differently:
+             * 1) When it enters screen, next pageDown will align its top with parent top;
+             * 2) When it leaves screen, next pageDown will align its bottom with parent bottom.
+             */
+            View child = getRecyclerView().getChildAt(i);
+            if (child.getHeight() > screenSize) {
+                if (orientationHelper.getDecoratedStart(child) > 0) {
+                    // Child view top is entering screen. Align its top with parent top.
+                    scrollDistance = orientationHelper.getDecoratedStart(child);
+                } else if (screenSize < orientationHelper.getDecoratedEnd(child)
+                        && orientationHelper.getDecoratedEnd(child) < 2 * screenSize) {
+                    // Child view bottom is about to enter screen - its distance to parent bottom
+                    // is less than a full scroll. Align child bottom with parent bottom.
+                    scrollDistance = orientationHelper.getDecoratedEnd(child) - screenSize;
+                }
+                // There can be two items that are longer than the screen. We stop at the first one.
+                // This is affected by the iteration order.
+                break;
+            }
+        }
+
+        getRecyclerView().smoothScrollBy(0, scrollDistance);
+    }
+
+    /**
+     * Determines if scrollbar should be visible or not and shows/hides it accordingly. If this is
+     * being called as a result of adapter changes, it should be called after the new layout has
+     * been
+     * calculated because the method of determining scrollbar visibility uses the current layout.
+     * If
+     * this is called after an adapter change but before the new layout, the visibility
+     * determination
+     * may not be correct.
+     *
+     * @param animate {@code true} if the scrollbar should animate to its new position. {@code
+     * false}
+     * if no animation is used
+     */
+    private void updatePaginationButtons(boolean animate) {
+
+        boolean isAtStart = isAtStart();
+        boolean isAtEnd = isAtEnd();
+        RecyclerView.LayoutManager layoutManager = getRecyclerView().getLayoutManager();
+
+        if ((isAtStart && isAtEnd) || layoutManager == null || layoutManager.getItemCount() == 0) {
+            mScrollView.setVisibility(View.INVISIBLE);
+        } else {
+            mScrollView.setVisibility(View.VISIBLE);
+        }
+        setUpEnabled(!isAtStart);
+        setDownEnabled(!isAtEnd);
+
+        if (layoutManager == null) {
+            return;
+        }
+
+        if (layoutManager.canScrollVertically()) {
+            setParameters(
+                    getRecyclerView().computeVerticalScrollRange(),
+                    getRecyclerView().computeVerticalScrollOffset(),
+                    getRecyclerView().computeVerticalScrollExtent(),
+                    animate);
+        } else {
+            setParameters(
+                    getRecyclerView().computeHorizontalScrollRange(),
+                    getRecyclerView().computeHorizontalScrollOffset(),
+                    getRecyclerView().computeHorizontalScrollExtent(),
+                    animate);
+        }
+
+        mScrollView.invalidate();
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+    boolean isAtStart() {
+        return mSnapHelper.isAtStart(getRecyclerView().getLayoutManager());
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    boolean isAtEnd() {
+        return mSnapHelper.isAtEnd(getRecyclerView().getLayoutManager());
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarUxRestrictionsUtil.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarUxRestrictionsUtil.java
new file mode 100644
index 0000000..7e204bd
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/CarUxRestrictionsUtil.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_LIMIT_STRING_LENGTH;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictions.CarUxRestrictionsInfo;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.chassis.R;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Utility class to access Car Restriction Manager.
+ *
+ * <p>This class must be a singleton because only one listener can be registered with {@link
+ * CarUxRestrictionsManager} at a time, as documented in {@link
+ * CarUxRestrictionsManager#registerListener}.
+ */
+public class CarUxRestrictionsUtil {
+    private static final String TAG = "CarUxRestrictionsUtil";
+
+    private final Car mCarApi;
+    private CarUxRestrictionsManager mCarUxRestrictionsManager;
+    @NonNull
+    private CarUxRestrictions mCarUxRestrictions = getDefaultRestrictions();
+
+    private Set<OnUxRestrictionsChangedListener> mObservers;
+    private static CarUxRestrictionsUtil sInstance = null;
+
+    private CarUxRestrictionsUtil(Context context) {
+        CarUxRestrictionsManager.OnUxRestrictionsChangedListener listener =
+                (carUxRestrictions) -> {
+                    if (carUxRestrictions == null) {
+                        this.mCarUxRestrictions = getDefaultRestrictions();
+                    } else {
+                        this.mCarUxRestrictions = carUxRestrictions;
+                    }
+
+                    for (OnUxRestrictionsChangedListener observer : mObservers) {
+                        observer.onRestrictionsChanged(this.mCarUxRestrictions);
+                    }
+                };
+
+        mCarApi = Car.createCar(context);
+        mObservers = Collections.newSetFromMap(new WeakHashMap<>());
+
+        try {
+            mCarUxRestrictionsManager =
+                    (CarUxRestrictionsManager) mCarApi.getCarManager(
+                            Car.CAR_UX_RESTRICTION_SERVICE);
+            mCarUxRestrictionsManager.registerListener(listener);
+            listener.onUxRestrictionsChanged(
+                    mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
+        } catch (CarNotConnectedException | NullPointerException e) {
+            Log.e(TAG, "Car not connected", e);
+            // mCarUxRestrictions will be the default
+        }
+    }
+
+    @NonNull
+    private static CarUxRestrictions getDefaultRestrictions() {
+        return new CarUxRestrictions.Builder(
+                true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED, 0)
+                .build();
+    }
+
+    /** Listener interface used to update clients on UxRestrictions changes */
+    public interface OnUxRestrictionsChangedListener {
+        /** Called when CarUxRestrictions changes */
+        void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions);
+    }
+
+    /** Returns the singleton sInstance of this class */
+    @NonNull
+    public static CarUxRestrictionsUtil getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new CarUxRestrictionsUtil(context);
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Registers a listener on this class for updates to CarUxRestrictions. Multiple listeners may
+     * be
+     * registered.
+     */
+    public void register(OnUxRestrictionsChangedListener listener) {
+        mObservers.add(listener);
+        listener.onRestrictionsChanged(mCarUxRestrictions);
+    }
+
+    /** Unregisters a registered listener */
+    public void unregister(OnUxRestrictionsChangedListener listener) {
+        mObservers.remove(listener);
+    }
+
+    /**
+     * Returns whether any of the given flags is blocked by the current restrictions. If null is
+     * given, the method returns true for safety.
+     */
+    public static boolean isRestricted(
+            @CarUxRestrictionsInfo int restrictionFlags, @Nullable CarUxRestrictions uxr) {
+        return (uxr == null) || ((uxr.getActiveRestrictions() & restrictionFlags) != 0);
+    }
+
+    /**
+     * Complies the input string with the given UX restrictions. Returns the original string if
+     * already compliant, otherwise a shortened ellipsized string.
+     */
+    public static String complyString(Context context, String str, CarUxRestrictions uxr) {
+
+        if (isRestricted(UX_RESTRICTIONS_LIMIT_STRING_LENGTH, uxr)) {
+            int maxLength =
+                    uxr == null
+                            ? context.getResources().getInteger(
+                            R.integer.chassis_default_max_string_length)
+                            : uxr.getMaxRestrictedStringLength();
+
+            if (str.length() > maxLength) {
+                return str.substring(0, maxLength) + context.getString(R.string.chassis_ellipsis);
+            }
+        }
+
+        return str;
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerView.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerView.java
new file mode 100644
index 0000000..7f76e16
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerView.java
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.chassis.R;
+import com.android.car.chassis.pagedrecyclerview.decorations.grid.GridDividerItemDecoration;
+import com.android.car.chassis.pagedrecyclerview.decorations.grid.GridOffsetItemDecoration;
+import com.android.car.chassis.pagedrecyclerview.decorations.linear.LinearDividerItemDecoration;
+import com.android.car.chassis.pagedrecyclerview.decorations.linear.LinearOffsetItemDecoration;
+import com.android.car.chassis.pagedrecyclerview.decorations.linear.LinearOffsetItemDecoration.OffsetPosition;
+
+import java.lang.annotation.Retention;
+
+/**
+ * View that extends a {@link RecyclerView} and creates a nested {@code RecyclerView} which could
+ * potentially include a scrollbar that has page up and down arrows. Interaction with this view is
+ * similar to a {@code RecyclerView} as it takes the same adapter and the layout manager.
+ */
+public final class PagedRecyclerView extends RecyclerView {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PagedRecyclerView";
+
+    private final CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener;
+
+    private boolean mScrollBarEnabled;
+    private int mScrollBarContainerWidth;
+    @ScrollBarPosition
+    private int mScrollBarPosition;
+    private boolean mScrollBarAboveRecyclerView;
+    private String mScrollBarClass;
+    private boolean mFullyInitialized;
+    private float mScrollBarPaddingStart;
+    private float mScrollBarPaddingEnd;
+    private Context mContext;
+
+    @Gutter
+    private int mGutter;
+    private int mGutterSize;
+    private RecyclerView mNestedRecyclerView;
+    private Adapter<?> mAdapter;
+    private ScrollBar mScrollBar;
+
+    private GridOffsetItemDecoration mOffsetItemDecoration;
+    private GridDividerItemDecoration mDividerItemDecoration;
+    @PagedRecyclerViewLayout
+    int mPagedRecyclerViewLayout;
+    private int mNumOfColumns;
+
+    /**
+     * The possible values for @{link #setGutter}. The default value is actually {@link
+     * PagedRecyclerView.Gutter#BOTH}.
+     */
+    @IntDef({
+            Gutter.NONE,
+            Gutter.START,
+            Gutter.END,
+            Gutter.BOTH,
+    })
+    @Retention(SOURCE)
+    public @interface Gutter {
+        /**
+         * No gutter on either side of the list items. The items will span the full width of the
+         * RecyclerView
+         */
+        int NONE = 0;
+
+        /** Include a gutter only on the start side (that is, the same side as the scroll bar). */
+        int START = 1;
+
+        /** Include a gutter only on the end side (that is, the opposite side of the scroll bar). */
+        int END = 2;
+
+        /** Include a gutter on both sides of the list items. This is the default behaviour. */
+        int BOTH = 3;
+    }
+
+    /**
+     * The possible values for setScrollbarPosition. The default value is actually {@link
+     * PagedRecyclerView.ScrollBarPosition#START}.
+     */
+    @IntDef({
+            ScrollBarPosition.START,
+            ScrollBarPosition.END,
+    })
+    @Retention(SOURCE)
+    public @interface ScrollBarPosition {
+        /** Position the scrollbar to the left of the screen. This is default. */
+        int START = 0;
+
+        /** Position scrollbar to the right of the screen. */
+        int END = 2;
+    }
+
+    /**
+     * The possible values for setScrollbarPosition. The default value is actually {@link
+     * PagedRecyclerViewLayout#LINEAR}.
+     */
+    @IntDef({
+            PagedRecyclerViewLayout.LINEAR,
+            PagedRecyclerViewLayout.GRID,
+    })
+    @Retention(SOURCE)
+    public @interface PagedRecyclerViewLayout {
+        /** Position the scrollbar to the left of the screen. This is default. */
+        int LINEAR = 0;
+
+        /** Position scrollbar to the right of the screen. */
+        int GRID = 2;
+    }
+
+    /**
+     * Interface for a {@link RecyclerView.Adapter} to cap the number of items.
+     *
+     * <p>NOTE: it is still up to the adapter to use maxItems in {@link
+     * RecyclerView.Adapter#getItemCount()}.
+     *
+     * <p>the recommended way would be with:
+     *
+     * <pre>{@code
+     * {@literal@}Override
+     * public int getItemCount() {
+     *   return Math.min(super.getItemCount(), mMaxItems);
+     * }
+     * }</pre>
+     */
+    public interface ItemCap {
+        /** A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit. */
+        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);
+    }
+
+    /**
+     * Custom layout manager for the outer recyclerview. Since paddings should be applied by the
+     * inner
+     * recycler view within its bounds, this layout manager should always have 0 padding.
+     */
+    private static class PagedRecyclerViewLayoutManager extends LinearLayoutManager {
+        PagedRecyclerViewLayoutManager(Context context) {
+            super(context);
+        }
+
+        @Override
+        public int getPaddingTop() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingBottom() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingStart() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingEnd() {
+            return 0;
+        }
+
+        @Override
+        public boolean canScrollHorizontally() {
+            return false;
+        }
+
+        @Override
+        public boolean canScrollVertically() {
+            return false;
+        }
+    }
+
+    /**
+     * Custom layout manager for the outer recyclerview. Since paddings should be applied by the
+     * inner
+     * recycler view within its bounds, this layout manager should always have 0 padding.
+     */
+    private static class GridPagedRecyclerViewLayoutManager extends GridLayoutManager {
+        GridPagedRecyclerViewLayoutManager(Context context, int numOfColumns) {
+            super(context, numOfColumns);
+        }
+
+        @Override
+        public int getPaddingTop() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingBottom() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingStart() {
+            return 0;
+        }
+
+        @Override
+        public int getPaddingEnd() {
+            return 0;
+        }
+    }
+
+    public PagedRecyclerView(@NonNull Context context) {
+        this(context, null, 0);
+    }
+
+    public PagedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PagedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+        mListener = this::updateCarUxRestrictions;
+
+        init(context, attrs, defStyle);
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+        TypedArray a =
+                context.obtainStyledAttributes(
+                        attrs, R.styleable.PagedRecyclerView, defStyleAttr,
+                        R.style.PagedRecyclerView);
+
+        mScrollBarEnabled = context.getResources().getBoolean(R.bool.chassis_scrollbar_enable);
+        mFullyInitialized = false;
+
+        if (!mScrollBarEnabled) {
+            a.recycle();
+            mFullyInitialized = true;
+            return;
+        }
+
+        mNestedRecyclerView =
+                new RecyclerView(context, attrs, R.style.PagedRecyclerView_NestedRecyclerView);
+
+        mScrollBarPaddingStart =
+                context.getResources().getDimension(R.dimen.chassis_scrollbar_padding_start);
+        mScrollBarPaddingEnd =
+                context.getResources().getDimension(R.dimen.chassis_scrollbar_padding_end);
+
+        mPagedRecyclerViewLayout =
+                a.getInt(R.styleable.PagedRecyclerView_layoutStyle, PagedRecyclerViewLayout.LINEAR);
+        mNumOfColumns = a.getInt(R.styleable.PagedRecyclerView_numOfColumns, /* defValue= */ 2);
+        boolean enableDivider =
+                a.getBoolean(R.styleable.PagedRecyclerView_enableDivider, /* defValue= */ true);
+
+        if (mPagedRecyclerViewLayout == PagedRecyclerViewLayout.LINEAR) {
+
+            int linearTopOffset =
+                    a.getInteger(R.styleable.PagedRecyclerView_startOffset, /* defValue= */ 0);
+            int linearBottomOffset =
+                    a.getInteger(R.styleable.PagedRecyclerView_endOffset, /* defValue= */ 0);
+
+            if (enableDivider) {
+                RecyclerView.ItemDecoration dividerItemDecoration =
+                        new LinearDividerItemDecoration(
+                                context.getDrawable(R.drawable.chassis_pagedrecyclerview_divider));
+                super.addItemDecoration(dividerItemDecoration);
+            }
+            RecyclerView.ItemDecoration topOffsetItemDecoration =
+                    new LinearOffsetItemDecoration(linearTopOffset, OffsetPosition.START);
+            super.addItemDecoration(topOffsetItemDecoration);
+
+            RecyclerView.ItemDecoration bottomOffsetItemDecoration =
+                    new LinearOffsetItemDecoration(linearBottomOffset, OffsetPosition.END);
+            super.addItemDecoration(bottomOffsetItemDecoration);
+        } else {
+
+            int gridTopOffset =
+                    a.getInteger(R.styleable.PagedRecyclerView_startOffset, /* defValue= */ 0);
+            int gridBottomOffset =
+                    a.getInteger(R.styleable.PagedRecyclerView_endOffset, /* defValue= */ 0);
+
+            if (enableDivider) {
+                mDividerItemDecoration =
+                        new GridDividerItemDecoration(
+                                context.getDrawable(R.drawable.divider),
+                                context.getDrawable(R.drawable.divider),
+                                mNumOfColumns);
+                super.addItemDecoration(mDividerItemDecoration);
+            }
+
+            mOffsetItemDecoration =
+                    new GridOffsetItemDecoration(gridTopOffset, mNumOfColumns,
+                            OffsetPosition.START);
+            super.addItemDecoration(mOffsetItemDecoration);
+
+            GridOffsetItemDecoration bottomOffsetItemDecoration =
+                    new GridOffsetItemDecoration(gridBottomOffset, mNumOfColumns,
+                            OffsetPosition.END);
+            super.addItemDecoration(bottomOffsetItemDecoration);
+        }
+
+        super.setLayoutManager(new PagedRecyclerViewLayoutManager(context));
+        super.setAdapter(new PagedRecyclerViewAdapter());
+        super.setNestedScrollingEnabled(false);
+        super.setClipToPadding(false);
+
+        // Gutter
+        mGutter = context.getResources().getInteger(R.integer.chassis_scrollbar_gutter);
+        mGutterSize = getResources().getDimensionPixelSize(R.dimen.chassis_scrollbar_margin);
+
+        mScrollBarContainerWidth =
+                (int) context.getResources().getDimension(
+                        R.dimen.chassis_scrollbar_container_width);
+
+        mScrollBarPosition = context.getResources().getInteger(
+                R.integer.chassis_scrollbar_position);
+
+        mScrollBarAboveRecyclerView =
+                context.getResources().getBoolean(R.bool.chassis_scrollbar_above_recycler_view);
+        mScrollBarClass = context.getResources().getString(R.string.chassis_scrollbar_component);
+        a.recycle();
+        this.mContext = context;
+        // Apply inner RV layout changes after the layout has been calculated for this view.
+        this.getViewTreeObserver()
+                .addOnGlobalLayoutListener(
+                        new OnGlobalLayoutListener() {
+                            @Override
+                            public void onGlobalLayout() {
+                                // View holder layout is still pending.
+                                if (PagedRecyclerView.this.findViewHolderForAdapterPosition(0)
+                                        == null) {
+                                    return;
+                                }
+
+                                PagedRecyclerView.this.getViewTreeObserver()
+                                        .removeOnGlobalLayoutListener(this);
+                                initNestedRecyclerView();
+                                setNestedViewLayout();
+
+                                mNestedRecyclerView
+                                        .getViewTreeObserver()
+                                        .addOnGlobalLayoutListener(
+                                                new OnGlobalLayoutListener() {
+                                                    @Override
+                                                    public void onGlobalLayout() {
+                                                        mNestedRecyclerView
+                                                                .getViewTreeObserver()
+                                                                .removeOnGlobalLayoutListener(this);
+                                                        ViewGroup.LayoutParams params =
+                                                                getLayoutParams();
+                                                        params.height = getMeasuredHeight();
+                                                        setLayoutParams(params);
+                                                        createScrollBarFromConfig();
+                                                        mFullyInitialized = true;
+                                                    }
+                                                });
+                            }
+                        });
+    }
+
+    /**
+     * Returns {@code true} if the {@PagedRecyclerView} is fully drawn. Using a global layout
+     * mListener
+     * may not necessarily signify that this view is fully drawn (i.e. when the scrollbar is
+     * enabled).
+     * This is because the inner views (scrollbar and inner recycler view) are drawn after the
+     * outer
+     * views are finished.
+     */
+    public boolean fullyInitialized() {
+        return mFullyInitialized;
+    }
+
+    /** Sets the number of columns in which grid needs to be divided. */
+    public void setNumOfColumns(int numberOfColumns) {
+        mNumOfColumns = numberOfColumns;
+        mOffsetItemDecoration.setNumOfColumns(mNumOfColumns);
+        mDividerItemDecoration.setNumOfColumns(mNumOfColumns);
+    }
+
+    /**
+     * Returns the {@link LayoutManager} for the {@link RecyclerView} displaying the content.
+     *
+     * <p>In cases where the scroll bar is visible and the nested {@link RecyclerView} is displaying
+     * content, {@link #getLayoutManager()} cannot be used because it returns the {@link
+     * LayoutManager} of the outer {@link RecyclerView}. {@link #getLayoutManager()} could not be
+     * overridden to return the effective manager due to interference with accessibility node tree
+     * traversal.
+     */
+    @Nullable
+    public LayoutManager getEffectiveLayoutManager() {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.getLayoutManager();
+        }
+        return super.getLayoutManager();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mCarUxRestrictionsUtil.register(mListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mCarUxRestrictionsUtil.unregister(mListener);
+    }
+
+    private void updateCarUxRestrictions(CarUxRestrictions carUxRestrictions) {
+        // If the adapter does not implement ItemCap, then the max items on it cannot be updated.
+        if (!(mAdapter instanceof ItemCap)) {
+            return;
+        }
+
+        int maxItems = ItemCap.UNLIMITED;
+        if ((carUxRestrictions.getActiveRestrictions()
+                & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT)
+                != 0) {
+            maxItems = carUxRestrictions.getMaxCumulativeContentItems();
+        }
+
+        int originalCount = mAdapter.getItemCount();
+        ((ItemCap) mAdapter).setMaxItems(maxItems);
+        int newCount = mAdapter.getItemCount();
+
+        if (newCount == originalCount) {
+            return;
+        }
+
+        if (newCount < originalCount) {
+            mAdapter.notifyItemRangeRemoved(newCount, originalCount - newCount);
+        } else {
+            mAdapter.notifyItemRangeInserted(originalCount, newCount - originalCount);
+        }
+    }
+
+    @Override
+    public void setClipToPadding(boolean clipToPadding) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setClipToPadding(clipToPadding);
+        } else {
+            super.setClipToPadding(clipToPadding);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void setAdapter(@Nullable Adapter adapter) {
+
+        if (mPagedRecyclerViewLayout == PagedRecyclerViewLayout.LINEAR) {
+            mNestedRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
+        } else {
+            mNestedRecyclerView.setLayoutManager(
+                    new GridPagedRecyclerViewLayoutManager(mContext, mNumOfColumns));
+            setNumOfColumns(mNumOfColumns);
+        }
+
+        this.mAdapter = adapter;
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setAdapter(adapter);
+        } else {
+            super.setAdapter(adapter);
+        }
+    }
+
+    @Nullable
+    @Override
+    public Adapter<?> getAdapter() {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.getAdapter();
+        }
+        return super.getAdapter();
+    }
+
+    @Override
+    public void setLayoutManager(@Nullable LayoutManager layout) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setLayoutManager(layout);
+        } else {
+            super.setLayoutManager(layout);
+        }
+    }
+
+    @Override
+    public void setOnScrollChangeListener(OnScrollChangeListener l) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setOnScrollChangeListener(l);
+        } else {
+            super.setOnScrollChangeListener(l);
+        }
+    }
+
+    @Override
+    public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setVerticalFadingEdgeEnabled(verticalFadingEdgeEnabled);
+        } else {
+            super.setVerticalFadingEdgeEnabled(verticalFadingEdgeEnabled);
+        }
+    }
+
+    @Override
+    public void setFadingEdgeLength(int length) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setFadingEdgeLength(length);
+        } else {
+            super.setFadingEdgeLength(length);
+        }
+    }
+
+    @Override
+    public void addItemDecoration(@NonNull ItemDecoration decor, int index) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.addItemDecoration(decor, index);
+        } else {
+            super.addItemDecoration(decor, index);
+        }
+    }
+
+    @Override
+    public void addItemDecoration(@NonNull ItemDecoration decor) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.addItemDecoration(decor);
+        } else {
+            super.addItemDecoration(decor);
+        }
+    }
+
+    @Override
+    public void setItemAnimator(@Nullable ItemAnimator animator) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setItemAnimator(animator);
+        } else {
+            super.setItemAnimator(animator);
+        }
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setPadding(left, top, right, bottom);
+            if (mScrollBar != null) {
+                mScrollBar.requestLayout();
+            }
+        } else {
+            super.setPadding(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setPaddingRelative(start, top, end, bottom);
+            if (mScrollBar != null) {
+                mScrollBar.requestLayout();
+            }
+        } else {
+            super.setPaddingRelative(start, top, end, bottom);
+        }
+    }
+
+    @Override
+    public ViewHolder findViewHolderForLayoutPosition(int position) {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.findViewHolderForLayoutPosition(position);
+        } else {
+            return super.findViewHolderForLayoutPosition(position);
+        }
+    }
+
+    @Override
+    public ViewHolder findContainingViewHolder(View view) {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.findContainingViewHolder(view);
+        } else {
+            return super.findContainingViewHolder(view);
+        }
+    }
+
+    @Override
+    @Nullable
+    public View findChildViewUnder(float x, float y) {
+        if (mScrollBarEnabled) {
+            return mNestedRecyclerView.findChildViewUnder(x, y);
+        } else {
+            return super.findChildViewUnder(x, y);
+        }
+    }
+
+    @Override
+    public void addOnScrollListener(@NonNull OnScrollListener listener) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.addOnScrollListener(listener);
+        } else {
+            super.addOnScrollListener(listener);
+        }
+    }
+
+    @Override
+    public void removeOnScrollListener(@NonNull OnScrollListener listener) {
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.removeOnScrollListener(listener);
+        } else {
+            super.removeOnScrollListener(listener);
+        }
+    }
+
+    @Override
+    public int getPaddingStart() {
+        return mScrollBarEnabled ? mNestedRecyclerView.getPaddingStart() : super.getPaddingStart();
+    }
+
+    @Override
+    public int getPaddingEnd() {
+        return mScrollBarEnabled ? mNestedRecyclerView.getPaddingEnd() : super.getPaddingEnd();
+    }
+
+    @Override
+    public int getPaddingTop() {
+        return mScrollBarEnabled ? mNestedRecyclerView.getPaddingTop() : super.getPaddingTop();
+    }
+
+    @Override
+    public int getPaddingBottom() {
+        return mScrollBarEnabled ? mNestedRecyclerView.getPaddingBottom()
+                : super.getPaddingBottom();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.setVisibility(visibility);
+        }
+    }
+
+    private void initNestedRecyclerView() {
+        PagedRecyclerViewAdapter.NestedRowViewHolder vh =
+                (PagedRecyclerViewAdapter.NestedRowViewHolder)
+                        this.findViewHolderForAdapterPosition(0);
+        if (vh == null) {
+            throw new Error("Outer RecyclerView failed to initialize.");
+        }
+
+        vh.frameLayout.addView(mNestedRecyclerView);
+    }
+
+    private void createScrollBarFromConfig() {
+        if (DEBUG) {
+            Log.d(TAG, "createScrollBarFromConfig");
+        }
+
+        Class<?> cls;
+        try {
+            cls = getContext().getClassLoader().loadClass(mScrollBarClass);
+        } catch (Throwable t) {
+            throw andLog("Error loading scroll bar component: " + mScrollBarClass, t);
+        }
+        try {
+            mScrollBar = (ScrollBar) cls.getDeclaredConstructor().newInstance();
+        } catch (Throwable t) {
+            throw andLog("Error creating scroll bar component: " + mScrollBarClass, t);
+        }
+
+        mScrollBar.initialize(
+                mNestedRecyclerView, mScrollBarContainerWidth, mScrollBarPosition,
+                mScrollBarAboveRecyclerView);
+
+        mScrollBar.setPadding((int) mScrollBarPaddingStart, (int) mScrollBarPaddingEnd);
+
+        if (DEBUG) {
+            Log.d(TAG, "started " + mScrollBar.getClass().getSimpleName());
+        }
+    }
+
+    /**
+     * Set the nested view's layout to the specified value.
+     *
+     * <p>The mGutter is the space to the start/end of the list view items and will be equal in size
+     * to
+     * the scroll bars. By default, there is a mGutter to both the left and right of the list view
+     * items, to account for the scroll bar.
+     */
+    private void setNestedViewLayout() {
+        int startMargin = 0;
+        int endMargin = 0;
+        if ((mGutter & Gutter.START) != 0) {
+            startMargin = mGutterSize;
+        }
+        if ((mGutter & Gutter.END) != 0) {
+            endMargin = mGutterSize;
+        }
+
+        MarginLayoutParams layoutParams =
+                (MarginLayoutParams) mNestedRecyclerView.getLayoutParams();
+
+        layoutParams.setMarginStart(startMargin);
+        layoutParams.setMarginEnd(endMargin);
+
+        layoutParams.height = LayoutParams.MATCH_PARENT;
+        layoutParams.width = super.getLayoutManager().getWidth() - startMargin - endMargin;
+        // requestLayout() isn't sufficient because we also need to resolveLayoutParams().
+        mNestedRecyclerView.setLayoutParams(layoutParams);
+
+        // If there's a mGutter, set ClipToPadding to false so that CardView's shadow will still
+        // appear outside of the padding.
+        mNestedRecyclerView.setClipToPadding(startMargin == 0 && endMargin == 0);
+    }
+
+    private static RuntimeException andLog(String msg, Throwable t) {
+        Log.e(TAG, msg, t);
+        throw new RuntimeException(msg, t);
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState, getContext());
+        if (mScrollBarEnabled) {
+            mNestedRecyclerView.saveHierarchyState(ss.mNestedRecyclerViewState);
+        }
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            Log.w(TAG, "onRestoreInstanceState called with an unsupported state");
+            super.onRestoreInstanceState(state);
+        } else {
+            SavedState ss = (SavedState) state;
+            super.onRestoreInstanceState(ss.getSuperState());
+            if (mScrollBarEnabled) {
+                mNestedRecyclerView.restoreHierarchyState(ss.mNestedRecyclerViewState);
+            }
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        SparseArray<Parcelable> mNestedRecyclerViewState;
+        Context mContext;
+
+        SavedState(Parcelable superState, Context c) {
+            super(superState);
+            mContext = c;
+            mNestedRecyclerViewState = new SparseArray<>();
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mNestedRecyclerViewState = in.readSparseArray(mContext.getClassLoader());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeSparseArray(mNestedRecyclerViewState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerViewAdapter.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerViewAdapter.java
new file mode 100644
index 0000000..f3167ea
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedRecyclerViewAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.chassis.R;
+
+/** The adapter for the parent recyclerview in {@link PagedRecyclerView} widget. */
+final class PagedRecyclerViewAdapter
+        extends RecyclerView.Adapter<PagedRecyclerViewAdapter.NestedRowViewHolder> {
+
+    @Override
+    public PagedRecyclerViewAdapter.NestedRowViewHolder onCreateViewHolder(
+            ViewGroup parent, int viewType) {
+        View v =
+                LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.chassis_paged_recycler_view_item, parent, false);
+        return new NestedRowViewHolder(v);
+    }
+
+    // Replace the contents of a view (invoked by the layout manager). Intentionally left empty
+    // since this adapter is an empty shell for the nested recyclerview.
+    @Override
+    public void onBindViewHolder(NestedRowViewHolder holder, int position) {
+    }
+
+    // Return the size of your dataset (invoked by the layout manager)
+    @Override
+    public int getItemCount() {
+        return 1;
+    }
+
+    /** The viewholder class for the parent recyclerview. */
+    static class NestedRowViewHolder extends RecyclerView.ViewHolder {
+        public FrameLayout frameLayout;
+
+        NestedRowViewHolder(View view) {
+            super(view);
+            frameLayout = view.findViewById(R.id.nested_recycler_view_layout);
+        }
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSmoothScroller.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSmoothScroller.java
new file mode 100644
index 0000000..910e370
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSmoothScroller.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.chassis.R;
+
+/**
+ * Code drop from {androidx.car.widget.PagedSmoothScroller}
+ *
+ * <p>Custom {@link LinearSmoothScroller} that has:
+ *
+ * <ul>
+ * <li>Custom control over the speed of scrolls.
+ * <li>Scrolling that snaps to start of a child view.
+ * </ul>
+ */
+public class PagedSmoothScroller extends LinearSmoothScroller {
+    private float mMillisecondsPerInch;
+    private float mDecelerationTimeDivisor;
+
+    private Interpolator mInterpolator;
+
+    public PagedSmoothScroller(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mMillisecondsPerInch =
+                context.getResources().getFloat(R.dimen.chassis_scrollbar_milliseconds_per_inch);
+        mDecelerationTimeDivisor =
+                context.getResources().getFloat(
+                        R.dimen.chassis_scrollbar_deceleration_times_divisor);
+        mInterpolator =
+                new DecelerateInterpolator(
+                        context
+                                .getResources()
+                                .getFloat(
+                                        R.dimen.chassis_scrollbar_decelerate_interpolator_factor));
+    }
+
+    @Override
+    protected int getVerticalSnapPreference() {
+        // Returning SNAP_TO_START will ensure that if the top (start) row is partially visible it
+        // will be scrolled downward (END) to make the row fully visible.
+        return SNAP_TO_START;
+    }
+
+    @Override
+    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+        int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+
+        if (dy == 0) {
+            return;
+        }
+
+        final int time = calculateTimeForDeceleration(dy);
+        if (time > 0) {
+            action.update(0, -dy, time, mInterpolator);
+        }
+    }
+
+    @Override
+    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+        return mMillisecondsPerInch / displayMetrics.densityDpi;
+    }
+
+    @Override
+    protected int calculateTimeForDeceleration(int dx) {
+        return (int) Math.ceil(calculateTimeForScrolling(dx) / mDecelerationTimeDivisor);
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSnapHelper.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSnapHelper.java
new file mode 100644
index 0000000..0d31a76
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/PagedSnapHelper.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearSnapHelper;
+import androidx.recyclerview.widget.OrientationHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+
+/**
+ * Inspired by {@link androidx.car.widget.PagedSnapHelper}
+ *
+ * <p>Extension of a {@link LinearSnapHelper} that will snap to the start of the target child view
+ * to the start of the attached {@link RecyclerView}. The start of the view is defined as the top if
+ * the RecyclerView is scrolling vertically; it is defined as the left (or right if RTL) if the
+ * RecyclerView is scrolling horizontally.
+ */
+public class PagedSnapHelper extends LinearSnapHelper {
+
+    private final Context mContext;
+    private RecyclerView mRecyclerView;
+
+    public PagedSnapHelper(Context context) {
+        this.mContext = context;
+    }
+
+    // Orientation helpers are lazily created per LayoutManager.
+    @Nullable
+    private OrientationHelper mVerticalHelper;
+    @Nullable
+    private OrientationHelper mHorizontalHelper;
+
+    @Override
+    public int[] calculateDistanceToFinalSnap(
+            @NonNull LayoutManager layoutManager, @NonNull View targetView) {
+        int[] out = new int[2];
+        if (layoutManager.canScrollHorizontally()) {
+            out[0] = distanceToTopMargin(targetView, getHorizontalHelper(layoutManager));
+        } else {
+            out[0] = 0;
+        }
+
+        if (layoutManager.canScrollVertically()) {
+            out[1] = distanceToTopMargin(targetView, getVerticalHelper(layoutManager));
+        } else {
+            out[1] = 0;
+        }
+        return out;
+    }
+
+    @Override
+    public View findSnapView(LayoutManager layoutManager) {
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+
+        if (mRecyclerView.computeVerticalScrollRange() - mRecyclerView.computeVerticalScrollOffset()
+                <= orientationHelper.getTotalSpace()
+                + mRecyclerView.getPaddingTop()
+                + mRecyclerView.getPaddingBottom()) {
+            return null;
+        }
+
+        if (layoutManager.canScrollVertically()) {
+            return findTopView(layoutManager, getVerticalHelper(layoutManager));
+        } else if (layoutManager.canScrollHorizontally()) {
+            return findTopView(layoutManager, getHorizontalHelper(layoutManager));
+        }
+        return null;
+    }
+
+    private static int distanceToTopMargin(@NonNull View targetView, OrientationHelper helper) {
+        final int childTop = helper.getDecoratedStart(targetView);
+        final int containerTop = helper.getStartAfterPadding();
+        return childTop - containerTop;
+    }
+
+    /**
+     * Finds the view to snap to. The view to snap to is the child of the LayoutManager that is
+     * closest to the start of the RecyclerView. The "start" depends on if the LayoutManager is
+     * scrolling horizontally or vertically. If it is horizontally scrolling, then the start is the
+     * view on the left (right if RTL). Otherwise, it is the top-most view.
+     *
+     * @param layoutManager The current {@link RecyclerView.LayoutManager} for the attached
+     * RecyclerView.
+     * @return The View closest to the start of the RecyclerView.
+     */
+    private static View findTopView(LayoutManager layoutManager, OrientationHelper helper) {
+        int childCount = layoutManager.getChildCount();
+        if (childCount == 0) {
+            return null;
+        }
+
+        View closestChild = null;
+        int absClosest = Integer.MAX_VALUE;
+
+        for (int i = 0; i < childCount; i++) {
+            View child = layoutManager.getChildAt(i);
+            if (child == null) {
+                continue;
+            }
+            int absDistance = Math.abs(distanceToTopMargin(child, helper));
+
+            /** if child top is closer than previous closest, set it as closest */
+            if (absDistance < absClosest) {
+                absClosest = absDistance;
+                closestChild = child;
+            }
+        }
+        return closestChild;
+    }
+
+    /**
+     * Returns the percentage of the given view that is visible, relative to its containing
+     * RecyclerView.
+     *
+     * @param view The View to get the percentage visible of.
+     * @param helper An {@link OrientationHelper} to aid with calculation.
+     * @return A float indicating the percentage of the given view that is visible.
+     */
+    private static float getPercentageVisible(View view, OrientationHelper helper) {
+
+        int start = helper.getStartAfterPadding();
+        int end = helper.getEndAfterPadding();
+
+        int viewHeight = helper.getDecoratedMeasurement(view);
+
+        int viewStart = helper.getDecoratedStart(view);
+        int viewEnd = helper.getDecoratedEnd(view);
+
+        if (viewEnd < start) {
+            // The is outside of the bounds of the recyclerView.
+            return 0f;
+        } else if (viewStart >= start && viewEnd <= end) {
+            // The view is within the bounds of the RecyclerView, so it's fully visible.
+            return 1.f;
+        } else if (viewStart <= start && viewEnd >= end) {
+            // The view is larger than the height of the RecyclerView.
+            return 1.f - ((float) (Math.abs(viewStart) + Math.abs(viewEnd)) / viewHeight);
+        } else if (viewStart < start) {
+            // The view is above the start of the RecyclerView, so subtract the start offset
+            // from the total height.
+            return 1.f - ((float) Math.abs(viewStart) / helper.getDecoratedMeasurement(view));
+        } else {
+            // The view is below the end of the RecyclerView, so subtract the end offset from the
+            // total height.
+            return 1.f - ((float) Math.abs(viewEnd) / helper.getDecoratedMeasurement(view));
+        }
+    }
+
+    @Override
+    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+        this.mRecyclerView = recyclerView;
+        super.attachToRecyclerView(recyclerView);
+    }
+
+    /**
+     * Returns a scroller specific to this {@code PagedSnapHelper}. This scroller is used for all
+     * smooth scrolling operations, including flings.
+     *
+     * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached
+     * {@link
+     * RecyclerView}.
+     * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling.
+     */
+    @Override
+    protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
+        return new PagedSmoothScroller(mContext);
+    }
+
+    /**
+     * Calculate the estimated scroll distance in each direction given velocities on both axes. This
+     * method will clamp the maximum scroll distance so that a single fling will never scroll more
+     * than one page.
+     *
+     * @param velocityX Fling velocity on the horizontal axis.
+     * @param velocityY Fling velocity on the vertical axis.
+     * @return An array holding the calculated distances in x and y directions respectively.
+     */
+    @Override
+    public int[] calculateScrollDistance(int velocityX, int velocityY) {
+        int[] outDist = super.calculateScrollDistance(velocityX, velocityY);
+
+        if (mRecyclerView == null) {
+            return outDist;
+        }
+
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return outDist;
+        }
+
+        int lastChildPosition = isAtEnd(layoutManager) ? 0 : layoutManager.getChildCount() - 1;
+
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+        View lastChild = layoutManager.getChildAt(lastChildPosition);
+        float percentageVisible = getPercentageVisible(lastChild, orientationHelper);
+
+        int maxDistance = layoutManager.getHeight();
+        if (percentageVisible > 0.f) {
+            // The max and min distance is the total height of the RecyclerView minus the height of
+            // the last child. This ensures that each scroll will never scroll more than a single
+            // page on the RecyclerView. That is, the max scroll will make the last child the
+            // first child and vice versa when scrolling the opposite way.
+            maxDistance -= layoutManager.getDecoratedMeasuredHeight(lastChild);
+        }
+
+        int minDistance = -maxDistance;
+
+        outDist[0] = clamp(outDist[0], minDistance, maxDistance);
+        outDist[1] = clamp(outDist[1], minDistance, maxDistance);
+
+        return outDist;
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+    boolean isAtStart(RecyclerView.LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        View firstChild = layoutManager.getChildAt(0);
+        OrientationHelper orientationHelper =
+                layoutManager.canScrollVertically()
+                        ? getVerticalHelper(layoutManager)
+                        : getHorizontalHelper(layoutManager);
+
+        // Check that the first child is completely visible and is the first item in the list.
+        return orientationHelper.getDecoratedStart(firstChild)
+                >= orientationHelper.getStartAfterPadding()
+                && layoutManager.getPosition(firstChild) == 0;
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    public boolean isAtEnd(RecyclerView.LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        int childCount = layoutManager.getChildCount();
+        OrientationHelper orientationHelper =
+                layoutManager.canScrollVertically()
+                        ? getVerticalHelper(layoutManager)
+                        : getHorizontalHelper(layoutManager);
+
+        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+
+        // The list has reached the bottom if the last child that is visible is the last item
+        // in the list and it's fully shown.
+        return layoutManager.getPosition(lastVisibleChild) == (layoutManager.getItemCount() - 1)
+                && layoutManager.getDecoratedBottom(lastVisibleChild)
+                <= orientationHelper.getEndAfterPadding();
+    }
+
+    /**
+     * Returns an {@link OrientationHelper} that corresponds to the current scroll direction of the
+     * given {@link RecyclerView.LayoutManager}.
+     */
+    @NonNull
+    private OrientationHelper getOrientationHelper(
+            @NonNull RecyclerView.LayoutManager layoutManager) {
+        return layoutManager.canScrollVertically()
+                ? getVerticalHelper(layoutManager)
+                : getHorizontalHelper(layoutManager);
+    }
+
+    @NonNull
+    private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
+        if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) {
+            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
+        }
+        return mVerticalHelper;
+    }
+
+    @NonNull
+    private OrientationHelper getHorizontalHelper(
+            @NonNull RecyclerView.LayoutManager layoutManager) {
+        if (mHorizontalHelper == null || mHorizontalHelper.getLayoutManager() != layoutManager) {
+            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
+        }
+        return mHorizontalHelper;
+    }
+
+    /**
+     * Ensures that the given value falls between the range given by the min and max values. This
+     * method does not check that the min value is greater than or equal to the max value. If the
+     * parameters are not well-formed, this method's behavior is undefined.
+     *
+     * @param value The value to clamp.
+     * @param min The minimum value the given value can be.
+     * @param max The maximum value the given value can be.
+     * @return A number that falls between {@code min} or {@code max} or one of those values if the
+     * given value is less than or greater than {@code min} and {@code max} respectively.
+     */
+    private static int clamp(int value, int min, int max) {
+        return Math.max(min, Math.min(max, value));
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/ScrollBar.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/ScrollBar.java
new file mode 100644
index 0000000..40a673b
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/ScrollBar.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.chassis.pagedrecyclerview.PagedRecyclerView.ScrollBarPosition;
+
+/**
+ * An abstract class that defines required contract for a custom scroll bar for the {@link
+ * PagedRecyclerView}. All custom scroll bar must inherit from this class.
+ */
+public interface ScrollBar {
+    /**
+     * The concrete class should implement this method to initialize configuration of a scrollbar
+     * view.
+     */
+    void initialize(
+            RecyclerView recyclerView,
+            int scrollBarContainerWidth,
+            @ScrollBarPosition int scrollBarPosition,
+            boolean scrollBarAboveRecyclerView);
+
+    /**
+     * Requests layout of the scrollbar. Should be called when there's been a change that will
+     * affect
+     * the size of the scrollbar view.
+     */
+    void requestLayout();
+
+    /** Sets the padding of the scrollbar, relative to the padding of the RecyclerView. */
+    void setPadding(int padddingStart, int paddingEnd);
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridDividerItemDecoration.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridDividerItemDecoration.java
new file mode 100644
index 0000000..adf9f28
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridDividerItemDecoration.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview.decorations.grid;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/** Adds interior dividers to a RecyclerView with a GridLayoutManager. */
+public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final Drawable mHorizontalDivider;
+    private final Drawable mVerticalDivider;
+    private int mNumColumns;
+
+    /**
+     * Sole constructor. Takes in {@link Drawable} objects to be used as horizontal and vertical
+     * dividers.
+     *
+     * @param horizontalDivider A divider {@code Drawable} to be drawn on the rows of the grid of
+     * the
+     * RecyclerView
+     * @param verticalDivider A divider {@code Drawable} to be drawn on the columns of the grid of
+     * the
+     * RecyclerView
+     * @param numColumns The number of columns in the grid of the RecyclerView
+     */
+    public GridDividerItemDecoration(
+            Drawable horizontalDivider, Drawable verticalDivider, int numColumns) {
+        this.mHorizontalDivider = horizontalDivider;
+        this.mVerticalDivider = verticalDivider;
+        this.mNumColumns = numColumns;
+    }
+
+    /**
+     * Draws horizontal and/or vertical dividers onto the parent RecyclerView.
+     *
+     * @param canvas The {@link Canvas} onto which dividers will be drawn
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
+        drawVerticalDividers(canvas, parent);
+        drawHorizontalDividers(canvas, parent);
+    }
+
+    /**
+     * Determines the size and location of offsets between items in the parent RecyclerView.
+     *
+     * @param outRect The {@link Rect} of offsets to be added around the child view
+     * @param view The child view to be decorated with an offset
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void getItemOffsets(
+            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        outRect.set(
+                0, 0, mHorizontalDivider.getIntrinsicWidth(),
+                mHorizontalDivider.getIntrinsicHeight());
+    }
+
+    /**
+     * Adds horizontal dividers to a RecyclerView with a GridLayoutManager or its subclass.
+     *
+     * @param canvas The {@link Canvas} onto which dividers will be drawn
+     * @param parent The RecyclerView onto which dividers are being added
+     */
+    private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
+        int childCount = parent.getChildCount();
+        int rowCount = childCount / mNumColumns;
+        int lastRowChildCount = childCount % mNumColumns;
+
+        for (int i = 1; i < mNumColumns; i++) {
+            int lastRowChildIndex;
+            if (i < lastRowChildCount) {
+                lastRowChildIndex = i + (rowCount * mNumColumns);
+            } else {
+                lastRowChildIndex = i + ((rowCount - 1) * mNumColumns);
+            }
+
+            View firstRowChild = parent.getChildAt(i);
+            View lastRowChild = parent.getChildAt(lastRowChildIndex);
+
+            int dividerTop = firstRowChild.getTop();
+            int dividerRight = firstRowChild.getLeft();
+            int dividerLeft = dividerRight - mHorizontalDivider.getIntrinsicWidth();
+            int dividerBottom = lastRowChild.getBottom();
+
+            mHorizontalDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
+            mHorizontalDivider.draw(canvas);
+        }
+    }
+
+    public void setNumOfColumns(int numberOfColumns) {
+        mNumColumns = numberOfColumns;
+    }
+
+    /**
+     * Adds vertical dividers to a RecyclerView with a GridLayoutManager or its subclass.
+     *
+     * @param canvas The {@link Canvas} onto which dividers will be drawn
+     * @param parent The RecyclerView onto which dividers are being added
+     */
+    private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
+        double childCount = parent.getChildCount();
+        double rowCount = Math.ceil(childCount / mNumColumns);
+        int rightmostChildIndex;
+        for (int i = 1; i <= rowCount; i++) {
+            // we dont want the divider on top of first row.
+            if (i == 1) {
+                continue;
+            }
+            if (i == rowCount) {
+                rightmostChildIndex = ((i - 1) * mNumColumns) - 1;
+            } else {
+                rightmostChildIndex = (i * mNumColumns) - 1;
+            }
+
+            View leftmostChild = parent.getChildAt(mNumColumns * (i - 1));
+            View rightmostChild = parent.getChildAt(rightmostChildIndex);
+
+            // draws on top of each row.
+            int dividerLeft = leftmostChild.getLeft();
+            int dividerBottom = leftmostChild.getTop();
+            int dividerTop = dividerBottom - mVerticalDivider.getIntrinsicHeight();
+            int dividerRight = rightmostChild.getRight();
+
+            mVerticalDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
+            mVerticalDivider.draw(canvas);
+        }
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridOffsetItemDecoration.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridOffsetItemDecoration.java
new file mode 100644
index 0000000..e290763
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/grid/GridOffsetItemDecoration.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview.decorations.grid;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.IntDef;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.annotation.Retention;
+
+/** Adds an offset to the top of a RecyclerView with a GridLayoutManager or its subclass. */
+public class GridOffsetItemDecoration extends RecyclerView.ItemDecoration {
+
+    private int mOffsetPx;
+    private Drawable mOffsetDrawable;
+    private int mNumColumns;
+    @OffsetPosition
+    private final int mOffsetPosition;
+
+    /** The possible values for setScrollbarPosition. */
+    @IntDef({
+            OffsetPosition.START,
+            OffsetPosition.END,
+    })
+    @Retention(SOURCE)
+    public @interface OffsetPosition {
+        /** Position the offset to the start of the screen. */
+        int START = 0;
+
+        /** Position offset to the end of the screen. */
+        int END = 1;
+    }
+
+    /**
+     * Constructor that takes in the size of the offset to be added to the top of the RecyclerView.
+     *
+     * @param offsetPx The size of the offset to be added to the top of the RecyclerView in pixels
+     * @param numColumns The number of columns in the grid of the RecyclerView
+     * @param offsetPosition Position where offset needs to be applied.
+     */
+    public GridOffsetItemDecoration(int offsetPx, int numColumns, int offsetPosition) {
+        this.mOffsetPx = offsetPx;
+        this.mNumColumns = numColumns;
+        this.mOffsetPosition = offsetPosition;
+    }
+
+    /**
+     * Constructor that takes in a {@link Drawable} to be drawn at the top of the RecyclerView.
+     *
+     * @param offsetDrawable The {@code Drawable} to be added to the top of the RecyclerView
+     * @param numColumns The number of columns in the grid of the RecyclerView
+     */
+    public GridOffsetItemDecoration(Drawable offsetDrawable, int numColumns, int offsetPosition) {
+        this.mOffsetDrawable = offsetDrawable;
+        this.mNumColumns = numColumns;
+        this.mOffsetPosition = offsetPosition;
+    }
+
+    public void setNumOfColumns(int numberOfColumns) {
+        mNumColumns = numberOfColumns;
+    }
+
+    /**
+     * Determines the size and the location of the offset to be added to the top of the
+     * RecyclerView.
+     *
+     * @param outRect The {@link Rect} of offsets to be added around the child view
+     * @param view The child view to be decorated with an offset
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void getItemOffsets(
+            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+
+        if (mOffsetPosition == OffsetPosition.START) {
+            boolean childIsInTopRow = parent.getChildAdapterPosition(view) < mNumColumns;
+            if (childIsInTopRow) {
+                if (mOffsetPx > 0) {
+                    outRect.top = mOffsetPx;
+                } else if (mOffsetDrawable != null) {
+                    outRect.top = mOffsetDrawable.getIntrinsicHeight();
+                }
+            }
+            return;
+        }
+
+        int childCount = state.getItemCount();
+        int lastRowChildCount = getLastRowChildCount(childCount);
+
+        boolean childIsInBottomRow =
+                parent.getChildAdapterPosition(view) >= childCount - lastRowChildCount;
+        if (childIsInBottomRow) {
+            if (mOffsetPx > 0) {
+                outRect.bottom = mOffsetPx;
+            } else if (mOffsetDrawable != null) {
+                outRect.bottom = mOffsetDrawable.getIntrinsicHeight();
+            }
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDraw(c, parent, state);
+        if (mOffsetDrawable == null) {
+            return;
+        }
+
+        int parentLeft = parent.getPaddingLeft();
+        int parentRight = parent.getWidth() - parent.getPaddingRight();
+
+        if (mOffsetPosition == OffsetPosition.START) {
+
+            int parentTop = parent.getPaddingTop();
+            int offsetDrawableBottom = parentTop + mOffsetDrawable.getIntrinsicHeight();
+
+            mOffsetDrawable.setBounds(parentLeft, parentTop, parentRight, offsetDrawableBottom);
+            mOffsetDrawable.draw(c);
+            return;
+        }
+
+        int childCount = state.getItemCount();
+        int lastRowChildCount = getLastRowChildCount(childCount);
+
+        int offsetDrawableTop = 0;
+        int offsetDrawableBottom = 0;
+
+        for (int i = childCount - lastRowChildCount; i < childCount; i++) {
+            View child = parent.getChildAt(i);
+            offsetDrawableTop = child.getBottom();
+            offsetDrawableBottom = offsetDrawableTop + mOffsetDrawable.getIntrinsicHeight();
+        }
+
+        mOffsetDrawable.setBounds(parentLeft, offsetDrawableTop, parentRight, offsetDrawableBottom);
+        mOffsetDrawable.draw(c);
+    }
+
+    private int getLastRowChildCount(int itemCount) {
+        int lastRowChildCount = itemCount % mNumColumns;
+        if (lastRowChildCount == 0) {
+            lastRowChildCount = mNumColumns;
+        }
+
+        return lastRowChildCount;
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearDividerItemDecoration.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearDividerItemDecoration.java
new file mode 100644
index 0000000..b007cd3
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearDividerItemDecoration.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview.decorations.linear;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+/** Adds interior dividers to a RecyclerView with a LinearLayoutManager or its subclass. */
+public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
+
+    private final Drawable mDivider;
+    private int mOrientation;
+
+    /**
+     * Sole constructor. Takes in a {@link Drawable} to be used as the interior
+     * chassis_pagedrecyclerview_divider.
+     *
+     * @param divider A chassis_pagedrecyclerview_divider {@code Drawable} to be drawn on the
+     * RecyclerView
+     */
+    public LinearDividerItemDecoration(Drawable divider) {
+        this.mDivider = divider;
+    }
+
+    /**
+     * Draws horizontal or vertical dividers onto the parent RecyclerView.
+     *
+     * @param canvas The {@link Canvas} onto which dividers will be drawn
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
+        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
+            drawHorizontalDividers(canvas, parent);
+        } else if (mOrientation == LinearLayoutManager.VERTICAL) {
+            drawVerticalDividers(canvas, parent);
+        }
+    }
+
+    /**
+     * Determines the size and location of offsets between items in the parent RecyclerView.
+     *
+     * @param outRect The {@link Rect} of offsets to be added around the child view
+     * @param view The child view to be decorated with an offset
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void getItemOffsets(
+            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+
+        if (parent.getChildAdapterPosition(view) == 0) {
+            return;
+        }
+
+        mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
+        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
+            outRect.left = mDivider.getIntrinsicWidth();
+        } else if (mOrientation == LinearLayoutManager.VERTICAL) {
+            outRect.top = mDivider.getIntrinsicHeight();
+        }
+    }
+
+    /**
+     * Adds dividers to a RecyclerView with a LinearLayoutManager or its subclass oriented
+     * horizontally.
+     *
+     * @param canvas The {@link Canvas} onto which horizontal dividers will be drawn
+     * @param parent The RecyclerView onto which horizontal dividers are being added
+     */
+    private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
+        int parentTop = parent.getPaddingTop();
+        int parentBottom = parent.getHeight() - parent.getPaddingBottom();
+
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount - 1; i++) {
+            View child = parent.getChildAt(i);
+
+            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+            int parentLeft = child.getRight() + params.rightMargin;
+            int parentRight = parentLeft + mDivider.getIntrinsicWidth();
+
+            mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
+            mDivider.draw(canvas);
+        }
+    }
+
+    /**
+     * Adds dividers to a RecyclerView with a LinearLayoutManager or its subclass oriented
+     * vertically.
+     *
+     * @param canvas The {@link Canvas} onto which vertical dividers will be drawn
+     * @param parent The RecyclerView onto which vertical dividers are being added
+     */
+    private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
+        int parentLeft = parent.getPaddingLeft();
+        int parentRight = parent.getWidth() - parent.getPaddingRight();
+
+        int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount - 1; i++) {
+            View child = parent.getChildAt(i);
+
+            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+            int parentTop = child.getBottom() + params.bottomMargin;
+            int parentBottom = parentTop + mDivider.getIntrinsicHeight();
+
+            mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
+            mDivider.draw(canvas);
+        }
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearOffsetItemDecoration.java b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearOffsetItemDecoration.java
new file mode 100644
index 0000000..f011843
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/pagedrecyclerview/decorations/linear/LinearOffsetItemDecoration.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2019 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.chassis.pagedrecyclerview.decorations.linear;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.IntDef;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Adds an offset to the start of a RecyclerView using a LinearLayoutManager or its subclass.
+ *
+ * <p>If the RecyclerView.LayoutManager is oriented vertically, the offset will be added to the top
+ * of the RecyclerView. If the LayoutManager is oriented horizontally, the offset will be added to
+ * the left of the RecyclerView.
+ */
+public class LinearOffsetItemDecoration extends RecyclerView.ItemDecoration {
+
+    private int mOffsetPx;
+    private Drawable mOffsetDrawable;
+    private int mOrientation;
+    @OffsetPosition
+    private int mOffsetPosition;
+
+    /** The possible values for setScrollbarPosition. */
+    @IntDef({
+            OffsetPosition.START,
+            OffsetPosition.END,
+    })
+    @Retention(SOURCE)
+    public @interface OffsetPosition {
+        /** Position the offset to the start of the screen. */
+        int START = 0;
+
+        /** Position offset to the end of the screen. */
+        int END = 1;
+    }
+
+    /**
+     * Constructor that takes in the size of the offset to be added to the start of the
+     * RecyclerView.
+     *
+     * @param offsetPx The size of the offset to be added to the start of the RecyclerView in pixels
+     * @param offsetPosition Position where offset needs to be applied.
+     */
+    public LinearOffsetItemDecoration(int offsetPx, int offsetPosition) {
+        this.mOffsetPx = offsetPx;
+        this.mOffsetPosition = offsetPosition;
+    }
+
+    /**
+     * Constructor that takes in a {@link Drawable} to be drawn at the start of the RecyclerView.
+     *
+     * @param offsetDrawable The {@code Drawable} to be added to the start of the RecyclerView
+     */
+    public LinearOffsetItemDecoration(Drawable offsetDrawable) {
+        this.mOffsetDrawable = offsetDrawable;
+    }
+
+    /**
+     * Determines the size and location of the offset to be added to the start of the RecyclerView.
+     *
+     * @param outRect The {@link Rect} of offsets to be added around the child view
+     * @param view The child view to be decorated with an offset
+     * @param parent The RecyclerView onto which dividers are being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void getItemOffsets(
+            Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        super.getItemOffsets(outRect, view, parent, state);
+
+        if (mOffsetPosition == OffsetPosition.START && parent.getChildAdapterPosition(view) > 0) {
+            return;
+        }
+
+        int itemCount = state.getItemCount();
+        if (mOffsetPosition == OffsetPosition.END
+                && parent.getChildAdapterPosition(view) != itemCount - 1) {
+            return;
+        }
+
+        mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
+        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
+            if (mOffsetPx > 0) {
+                if (mOffsetPosition == OffsetPosition.START) {
+                    outRect.left = mOffsetPx;
+                } else {
+                    outRect.right = mOffsetPx;
+                }
+            } else if (mOffsetDrawable != null) {
+                if (mOffsetPosition == OffsetPosition.START) {
+                    outRect.left = mOffsetDrawable.getIntrinsicWidth();
+                } else {
+                    outRect.right = mOffsetDrawable.getIntrinsicWidth();
+                }
+            }
+        } else if (mOrientation == LinearLayoutManager.VERTICAL) {
+            if (mOffsetPx > 0) {
+                if (mOffsetPosition == OffsetPosition.START) {
+                    outRect.top = mOffsetPx;
+                } else {
+                    outRect.bottom = mOffsetPx;
+                }
+            } else if (mOffsetDrawable != null) {
+                if (mOffsetPosition == OffsetPosition.START) {
+                    outRect.top = mOffsetDrawable.getIntrinsicHeight();
+                } else {
+                    outRect.bottom = mOffsetDrawable.getIntrinsicHeight();
+                }
+            }
+        }
+    }
+
+    /**
+     * Draws horizontal or vertical offset onto the start of the parent RecyclerView.
+     *
+     * @param c The {@link Canvas} onto which an offset will be drawn
+     * @param parent The RecyclerView onto which an offset is being added
+     * @param state The current RecyclerView.State of the RecyclerView
+     */
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDraw(c, parent, state);
+        if (mOffsetDrawable == null) {
+            return;
+        }
+
+        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
+            drawOffsetHorizontal(c, parent);
+        } else if (mOrientation == LinearLayoutManager.VERTICAL) {
+            drawOffsetVertical(c, parent);
+        }
+    }
+
+    private void drawOffsetHorizontal(Canvas canvas, RecyclerView parent) {
+        int parentTop = parent.getPaddingTop();
+        int parentBottom = parent.getHeight() - parent.getPaddingBottom();
+        int parentLeft = 0;
+        int offsetDrawableRight = 0;
+
+        if (mOffsetPosition == OffsetPosition.START) {
+            parentLeft = parent.getPaddingLeft();
+            offsetDrawableRight = parentLeft + mOffsetDrawable.getIntrinsicWidth();
+        } else {
+            View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+            RecyclerView.LayoutParams lastChildLayoutParams =
+                    (RecyclerView.LayoutParams) lastChild.getLayoutParams();
+            parentLeft = lastChild.getRight() + lastChildLayoutParams.rightMargin;
+            offsetDrawableRight = parentLeft + mOffsetDrawable.getIntrinsicWidth();
+        }
+
+        mOffsetDrawable.setBounds(parentLeft, parentTop, offsetDrawableRight, parentBottom);
+        mOffsetDrawable.draw(canvas);
+    }
+
+    private void drawOffsetVertical(Canvas canvas, RecyclerView parent) {
+        int parentLeft = parent.getPaddingLeft();
+        int parentRight = parent.getWidth() - parent.getPaddingRight();
+
+        int parentTop = 0;
+        int offsetDrawableBottom = 0;
+
+        if (mOffsetPosition == OffsetPosition.START) {
+            parentTop = parent.getPaddingTop();
+            offsetDrawableBottom = parentTop + mOffsetDrawable.getIntrinsicHeight();
+        } else {
+            View lastChild = parent.getChildAt(parent.getChildCount() - 1);
+            RecyclerView.LayoutParams lastChildLayoutParams =
+                    (RecyclerView.LayoutParams) lastChild.getLayoutParams();
+            parentTop = lastChild.getBottom() + lastChildLayoutParams.bottomMargin;
+            offsetDrawableBottom = parentTop + mOffsetDrawable.getIntrinsicHeight();
+        }
+
+        mOffsetDrawable.setBounds(parentLeft, parentTop, parentRight, offsetDrawableBottom);
+        mOffsetDrawable.draw(canvas);
+    }
+}
diff --git a/car-chassis-lib/tests/Android.mk b/car-chassis-lib/tests/Android.mk
new file mode 100644
index 0000000..9f0a4e8
--- /dev/null
+++ b/car-chassis-lib/tests/Android.mk
@@ -0,0 +1,19 @@
+# Copyright (C) 2019 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all makefiles in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/car-chassis-lib/tests/paintbooth/Android.mk b/car-chassis-lib/tests/paintbooth/Android.mk
new file mode 100644
index 0000000..5683038
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/Android.mk
@@ -0,0 +1,46 @@
+#
+# Copyright (C) 2019 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.
+#
+
+ifneq ($(TARGET_BUILD_PDK), true)
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := PaintBooth
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+        car-chassis-lib
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/car-chassis-lib/tests/paintbooth/AndroidManifest-gradle.xml b/car-chassis-lib/tests/paintbooth/AndroidManifest-gradle.xml
new file mode 100644
index 0000000..a44ea36
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/AndroidManifest-gradle.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.chassis.paintbooth">
+
+  <application
+      android:icon="@drawable/ic_launcher"
+      android:label="@string/app_name"
+      android:theme="@style/ChassisTheme">
+    <activity
+        android:name=".MainActivity"
+        android:exported="true">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+    </activity>
+
+    <activity
+        android:name=".DialogSamples"
+        android:exported="false"
+        android:parentActivityName=".MainActivity"/>
+  </application>
+</manifest>
diff --git a/car-chassis-lib/tests/paintbooth/AndroidManifest.xml b/car-chassis-lib/tests/paintbooth/AndroidManifest.xml
new file mode 100644
index 0000000..097cf7e
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.chassis.paintbooth">
+
+  <uses-sdk
+      android:minSdkVersion="28"
+      android:targetSdkVersion="28"/>
+
+  <application
+      android:icon="@drawable/ic_launcher"
+      android:label="@string/app_name"
+      android:theme="@style/ChassisTheme">
+    <activity
+        android:name=".MainActivity"
+        android:exported="true">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+    </activity>
+
+    <activity
+        android:name=".DialogSamples"
+        android:exported="false"
+        android:parentActivityName=".MainActivity"/>
+  </application>
+</manifest>
diff --git a/car-chassis-lib/tests/paintbooth/build.gradle b/car-chassis-lib/tests/paintbooth/build.gradle
new file mode 100644
index 0000000..d56a776
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.android.car.chassis.paintbooth"
+        minSdkVersion 28
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest-gradle.xml'
+            java.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+}
+
+dependencies {
+    implementation project(':')
+    implementation 'androidx.annotation:annotation:1.1.0'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+}
diff --git a/car-chassis-lib/tests/paintbooth/res/drawable/ic_launcher.png b/car-chassis-lib/tests/paintbooth/res/drawable/ic_launcher.png
new file mode 100644
index 0000000..2af53a4
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/res/drawable/ic_launcher.png
Binary files differ
diff --git a/car-chassis-lib/tests/paintbooth/res/layout/dialog_samples.xml b/car-chassis-lib/tests/paintbooth/res/layout/dialog_samples.xml
new file mode 100644
index 0000000..29712a9
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/res/layout/dialog_samples.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dialog_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/dialog_activity_background_color">
+
+  <com.android.car.chassis.Toolbar
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      app:layout_constraintTop_toTopOf="parent"
+      app:title="@string/app_name"/>
+
+  <Button
+      android:id="@+id/show_dialog_bt"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Dialog"
+      app:layout_constraintBottom_toTopOf="@+id/show_dialog_only_positive_bt"
+      app:layout_constraintLeft_toLeftOf="parent"
+      app:layout_constraintRight_toRightOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/toolbar"/>
+
+  <Button
+      android:id="@+id/show_dialog_only_positive_bt"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Dialog with only positive button"
+      app:layout_constraintBottom_toTopOf="@+id/show_dialog_with_checkbox_bt"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/show_dialog_bt"/>
+
+  <Button
+      android:id="@+id/show_dialog_with_checkbox_bt"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Dialog With Checkbox"
+      app:layout_constraintBottom_toTopOf="@+id/show_dialog_without_title"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/show_dialog_only_positive_bt"/>
+
+  <Button
+      android:id="@+id/show_dialog_without_title"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Dialog without title"
+      app:layout_constraintBottom_toTopOf="@+id/show_toast"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/show_dialog_with_checkbox_bt"/>
+
+  <Button
+      android:id="@+id/show_toast"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Toast"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintTop_toBottomOf="@+id/show_dialog_without_title"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-chassis-lib/tests/paintbooth/res/layout/home_page.xml b/car-chassis-lib/tests/paintbooth/res/layout/home_page.xml
new file mode 100644
index 0000000..5b883ec
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/res/layout/home_page.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/home"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/dialog_activity_background_color">
+
+  <com.android.car.chassis.Toolbar
+      android:id="@+id/toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      app:layout_constraintTop_toTopOf="parent"
+      app:title="@string/app_name"
+      app:logo="@drawable/ic_launcher"/>
+
+  <Button
+      android:id="@+id/dialog_samples"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="Show Dialog Sample Page"
+      app:layout_constraintLeft_toLeftOf="parent"
+      app:layout_constraintRight_toRightOf="parent"
+      app:layout_constraintTop_toBottomOf="@id/toolbar"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-chassis-lib/tests/paintbooth/res/values/colors.xml b/car-chassis-lib/tests/paintbooth/res/values/colors.xml
new file mode 100644
index 0000000..6e93be6
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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>
+  <color name="dialog_activity_background_color">#ffff0c</color>
+</resources>
diff --git a/car-chassis-lib/tests/paintbooth/res/values/strings.xml b/car-chassis-lib/tests/paintbooth/res/values/strings.xml
new file mode 100644
index 0000000..9bfdb07
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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>
+  <!-- Application name [CHAR LIMIT=30] -->
+  <string name="app_name">Paint Booth (Gerrit)</string>
+</resources>
diff --git a/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/DialogSamples.java b/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/DialogSamples.java
new file mode 100644
index 0000000..693dbd9
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/DialogSamples.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 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.chassis.paintbooth;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.car.chassis.Toolbar;
+
+/**
+ * Activity that shows different dialogs from the device default theme.
+ */
+public class DialogSamples extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.dialog_samples);
+
+        Button showDialogButton = findViewById(R.id.show_dialog_bt);
+        Button showDialogOnlyPositiveButton = findViewById(R.id.show_dialog_only_positive_bt);
+        Button showDialogWithoutTitleButton = findViewById(R.id.show_dialog_without_title);
+        Button showDialogWithCheckboxButton = findViewById(R.id.show_dialog_with_checkbox_bt);
+        showDialogButton.setOnClickListener(v -> openDialog(false));
+        showDialogOnlyPositiveButton.setOnClickListener(v -> openDialogWithOnlyPositiveButton());
+        showDialogWithoutTitleButton.setOnClickListener(v -> openDialogWithoutTitle());
+        showDialogWithCheckboxButton.setOnClickListener(v -> openDialog(true));
+        Button showToast = findViewById(R.id.show_toast);
+        showToast.setOnClickListener(v -> showToast());
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        toolbar.setState(Toolbar.State.SUBPAGE);
+        toolbar.addListener(new Toolbar.Listener() {
+            @Override
+            public void onBack() {
+                finish();
+            }
+        });
+    }
+
+    private void openDialog(boolean showCheckbox) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+        if (showCheckbox) {
+            // Set Custom Title
+            TextView title = new TextView(this);
+            // Title Properties
+            title.setText("Custom Dialog Box");
+            builder.setCustomTitle(title);
+            builder.setMultiChoiceItems(
+                    new CharSequence[]{"I am a checkbox"},
+                    new boolean[]{false},
+                    (dialog, which, isChecked) -> {
+                    });
+        } else {
+            builder.setTitle("Standard Alert Dialog").setMessage("With a message to show.");
+        }
+
+        builder
+                .setPositiveButton("OK", (dialoginterface, i) -> {
+                })
+                .setNegativeButton("CANCEL", (dialog, which) -> {
+                });
+        builder.show();
+    }
+
+    private void openDialogWithOnlyPositiveButton() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Standard Alert Dialog").setMessage("With a message to show.");
+        builder.setPositiveButton("OK", (dialoginterface, i) -> {
+        });
+        builder.show();
+    }
+
+    private void openDialogWithoutTitle() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setMessage("I dont have a titile.");
+        builder
+                .setPositiveButton("OK", (dialoginterface, i) -> {
+                })
+                .setNegativeButton("CANCEL", (dialog, which) -> {
+                });
+        builder.show();
+    }
+
+    private void showToast() {
+        Toast.makeText(this, "Toast message looks like this", Toast.LENGTH_LONG).show();
+    }
+}
diff --git a/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/MainActivity.java b/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/MainActivity.java
new file mode 100644
index 0000000..b0f4844
--- /dev/null
+++ b/car-chassis-lib/tests/paintbooth/src/com/android/car/chassis/paintbooth/MainActivity.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.chassis.paintbooth;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+
+/**
+ * Paint booth app
+ */
+public class MainActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.home_page);
+
+        Button showDialogSamples = findViewById(R.id.dialog_samples);
+        showDialogSamples.setOnClickListener(v -> startSampleActivity(DialogSamples.class));
+    }
+
+    /**
+     * Launch the given sample activity
+     */
+    private boolean startSampleActivity(Class<?> cls) {
+        Intent intent = new Intent(this, cls);
+        startActivity(intent);
+        return true;
+    }
+}
diff --git a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
index ba17316..5ca60d1 100644
--- a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
+++ b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
@@ -147,11 +147,15 @@
 
             Bitmap myBitmap = getBitmapToFlag(context);
             Bitmap otherBitmap = other.getBitmapToFlag(context);
-            if ((myBitmap != null) && (myBitmap.equals(otherBitmap))) return true;
+            if ((myBitmap != null) || (otherBitmap != null)) {
+                return Objects.equals(myBitmap, otherBitmap);
+            }
 
             Uri myUri = getImageURI();
             Uri otherUri = other.getImageURI();
-            if ((myUri != null) && (myUri.equals(otherUri))) return true;
+            if ((myUri != null) || (otherUri != null)) {
+                return Objects.equals(myUri, otherUri);
+            }
 
             return getPlaceholderHash() == other.getPlaceholderHash();
         }
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSource.java b/car-media-common/src/com/android/car/media/common/source/MediaSource.java
index db2cc11..09dcbb5 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSource.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSource.java
@@ -38,10 +38,8 @@
 
 import com.android.car.apps.common.BitmapUtils;
 
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 
 /**
  * This represents a source of media content. It provides convenient methods to access media source
@@ -50,15 +48,6 @@
 public class MediaSource {
     private static final String TAG = "MediaSource";
 
-    /**
-     * Custom media sources which should not be templatized.
-     */
-    private static final Set<String> CUSTOM_MEDIA_SOURCES = new HashSet<>();
-
-    static {
-        CUSTOM_MEDIA_SOURCES.add("com.android.car.radio");
-    }
-
     @NonNull
     private final ComponentName mBrowseService;
     @NonNull
@@ -201,13 +190,6 @@
         return getRoundCroppedBitmap(BitmapUtils.fromDrawable(mIcon, null));
     }
 
-    /**
-     * Returns {@code true} iff this media source should not be templatized.
-     */
-    public boolean isCustom() {
-        return CUSTOM_MEDIA_SOURCES.contains(getPackageName());
-    }
-
     private static Bitmap getRoundCroppedBitmap(Bitmap bitmap) {
         Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
                 Bitmap.Config.ARGB_8888);
diff --git a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
index 8eebf95..398cfa1 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
@@ -90,14 +90,12 @@
     private InMemoryPhoneBook(Context context) {
         mContext = context;
 
-        // TODO(b/138749585): clean up filtering once contact cloud sync is disabled.
         QueryParam contactListQueryParam = new QueryParam(
                 ContactsContract.Data.CONTENT_URI,
                 null,
-                ContactsContract.Data.MIMETYPE + " = ? and "
-                        + ContactsContract.RawContacts.ACCOUNT_TYPE + " != ?",
+                ContactsContract.Data.MIMETYPE + " = ?",
                 new String[]{
-                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, "com.google"},
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
                 ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
         mContactListAsyncQueryLiveData = new AsyncQueryLiveData<List<Contact>>(mContext,
                 QueryParam.of(contactListQueryParam)) {