[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 43ec539e77 -s ours am: e168bb6139 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/libs/+/16637853

Change-Id: Ifff9b281bcd38f535476598d7049347529300169
diff --git a/car-ui-lib/.gitignore b/.gitignore
similarity index 86%
rename from car-ui-lib/.gitignore
rename to .gitignore
index 57df0a4..bdfa81c 100644
--- a/car-ui-lib/.gitignore
+++ b/.gitignore
@@ -3,9 +3,10 @@
 gradle-wrapper.properties
 
 # Gradle
-gradle/
 .gradle/
 build/
+gradle-app.setting
+.gradletasknamecache
 
 # IntelliJ
 .idea/
diff --git a/aaos-apps-gradle-project/build.gradle b/aaos-apps-gradle-project/build.gradle
new file mode 100644
index 0000000..f9289f9
--- /dev/null
+++ b/aaos-apps-gradle-project/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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:7.0.2'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+    tasks.withType(JavaCompile) {
+        // Compile with prebuilt android.jar.
+        options.compilerArgs.add('-Xbootclasspath/p:' + gradle.ext.lib_system_stubs)
+        // TODO: just pass -Xlint, to enable all the supported types of warnings.
+        options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+    }
+
+    buildDir = "/tmp/aaos-apps-gradle-build/${rootProject.name}/${project.name}"
+}
diff --git a/aaos-apps-gradle-project/gradle.properties b/aaos-apps-gradle-project/gradle.properties
new file mode 100644
index 0000000..9dad1c4
--- /dev/null
+++ b/aaos-apps-gradle-project/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/aaos-apps-gradle-project/gradle/wrapper/gradle-wrapper.jar b/aaos-apps-gradle-project/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..62d4c05
--- /dev/null
+++ b/aaos-apps-gradle-project/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/aaos-apps-gradle-project/gradlew b/aaos-apps-gradle-project/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/aaos-apps-gradle-project/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
+
+##############################################################################
+##
+##  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='"-Xmx64m" "-Xms64m"'
+
+# 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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
+
+exec "$JAVACMD" "$@"
diff --git a/aaos-apps-gradle-project/gradlew.bat b/aaos-apps-gradle-project/gradlew.bat
new file mode 100644
index 0000000..a9f778a
--- /dev/null
+++ b/aaos-apps-gradle-project/gradlew.bat
@@ -0,0 +1,104 @@
+@rem

+@rem Copyright 2015 the original author or authors.

+@rem

+@rem Licensed under the Apache License, Version 2.0 (the "License");

+@rem you may not use this file except in compliance with the License.

+@rem You may obtain a copy of the License at

+@rem

+@rem      https://www.apache.org/licenses/LICENSE-2.0

+@rem

+@rem Unless required by applicable law or agreed to in writing, software

+@rem distributed under the License is distributed on an "AS IS" BASIS,

+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+@rem See the License for the specific language governing permissions and

+@rem limitations under the License.

+@rem

+

+@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 Resolve any "." and ".." in APP_HOME to make it shorter.

+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

+

+@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="-Xmx64m" "-Xms64m"

+

+@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/aaos-apps-gradle-project/settings.gradle b/aaos-apps-gradle-project/settings.gradle
new file mode 100644
index 0000000..3e2f10e
--- /dev/null
+++ b/aaos-apps-gradle-project/settings.gradle
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+rootProject.name='AAOS Apps'
+
+gradle.ext.aaosLatestSDK = 31
+gradle.ext.aaosTargetSDK = 31
+
+gradle.ext.lib_car_system_stubs = rootDir.absolutePath + "../../../../../../prebuilts/sdk/" + gradle.ext.aaosLatestSDK + "/system/android.car-system-stubs.jar"
+gradle.ext.lib_system_stubs = rootDir.absolutePath + "../../../../../../prebuilts/sdk/" + gradle.ext.aaosLatestSDK + "/system/android.jar"
+
+//TODO: find something that works
+//def repoRootFolder = settingsDir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile()
+//gradle.ext.aaosBuildDir = new File(repoRootFolder, "/out/aaos-apps-gradle-project.build/")
+//gradle.ext.aaosBuildDir = new File('../../../../../out/aaos-apps-gradle-project.build/').getParentFile()
+
+
+include ':car-ui-lib'
+project(':car-ui-lib').projectDir = new File('../car-ui-lib/car-ui-lib')
+include ':PaintBooth'
+project(':PaintBooth').projectDir = new File('../car-ui-lib/paintbooth')
+include ':oem-apis'
+project(':oem-apis').projectDir = new File('../car-ui-lib/oem-apis')
+include ':plugin'
+project(':plugin').projectDir = new File('../car-ui-lib/referencedesign/plugin')
+include ':car-rotary-lib'
+project(':car-rotary-lib').projectDir = new File('../car-ui-lib/car-rotary-lib')
+
+include ':car-apps-common'
+project(':car-apps-common').projectDir = new File('../car-apps-common')
+
+include ':car-media-common'
+project(':car-media-common').projectDir = new File('../car-media-common')
+
+include ':car-uxr-client-lib'
+project(':car-uxr-client-lib').projectDir = new File('../car-uxr-client-lib')
+
+include ':car-telephony-common'
+project(':car-telephony-common').projectDir = new File('../car-telephony-common')
+
+include ':car-assist-lib'
+project(':car-assist-lib').projectDir = new File('../car-assist-lib')
+
+include ':car-messenger-common'
+project(':car-messenger-common').projectDir = new File('../car-messenger-common')
+include ':car-messenger-common:model'
+
+include ':car-media-app'
+project(':car-media-app').projectDir = new File('../../Media')
+
+include ':test-media-app'
+project(':test-media-app').projectDir = new File('../../tests/TestMediaApp')
+
+include ':test-rotary-playground'
+project(':test-rotary-playground').projectDir = new File('../../tests/RotaryPlayground')
+
+include ':test-rotary-ime'
+project(':test-rotary-ime').projectDir = new File('../../tests/RotaryIME')
+
+include ':car-dialer-app'
+project(':car-dialer-app').projectDir = new File('../../Dialer')
+include ':car-dialer-app:testing'
+include ':car-dialer-app:framework'
+
+include ':car-messenger-app'
+project(':car-messenger-app').projectDir = new File('../../Messenger')
diff --git a/car-apps-common/Android.bp b/car-apps-common/Android.bp
index 9518bc0..0f36e77 100644
--- a/car-apps-common/Android.bp
+++ b/car-apps-common/Android.bp
@@ -31,8 +31,9 @@
 
     libs: ["android.car-stubs",],
 
-    sdk_version: "system_current",
-    min_sdk_version: "28",
+    sdk_version: "current",
+    min_sdk_version: "28", // P
+    target_sdk_version: "31",
 
     static_libs: [
         "androidx.annotation_annotation",
@@ -44,5 +45,6 @@
         "androidx.recyclerview_recyclerview",
         "androidx-constraintlayout_constraintlayout-solver",
         "car-ui-lib",
+        "junit",
     ],
 }
diff --git a/car-apps-common/OWNERS b/car-apps-common/OWNERS
new file mode 100644
index 0000000..24f1b39
--- /dev/null
+++ b/car-apps-common/OWNERS
@@ -0,0 +1,3 @@
+# People who can approve changes for submission.
+arnaudberry@google.com
+yiqunw@google.com
diff --git a/car-apps-common/build.gradle b/car-apps-common/build.gradle
new file mode 100644
index 0000000..96bf5de
--- /dev/null
+++ b/car-apps-common/build.gradle
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Library-level build file
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+
+    // This is the gradle equivalent of the libs: ["android.car"] in the Android.bp
+    useLibrary 'android.car'
+}
+
+dependencies {
+
+    implementation "androidx.annotation:annotation:1.2.0"
+    implementation "androidx.cardview:cardview:1.0.0"
+    implementation "androidx.interpolator:interpolator:1.0.0"
+
+    def lifecycle_version = "2.2.0"
+    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+    // Not available in 2.3+
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
+    implementation 'androidx.recyclerview:recyclerview:1.2.1'
+
+    implementation "junit:junit:4.12"
+
+    implementation project(":car-ui-lib")
+}
diff --git a/car-apps-common/car-apps-common.iml b/car-apps-common/car-apps-common.iml
deleted file mode 100644
index 7b16a15..0000000
--- a/car-apps-common/car-apps-common.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
-  <component name="FacetManager">
-    <facet type="android" name="Android">
-      <configuration />
-    </facet>
-  </component>
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output />
-    <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="module" module-name="support.annotations" />
-  </component>
-</module>
\ No newline at end of file
diff --git a/car-theme-lib/AndroidManifest.xml b/car-apps-common/res/values/strings_no_translation.xml
similarity index 61%
rename from car-theme-lib/AndroidManifest.xml
rename to car-apps-common/res/values/strings_no_translation.xml
index 8e3dcc6..e30d4a3 100644
--- a/car-theme-lib/AndroidManifest.xml
+++ b/car-apps-common/res/values/strings_no_translation.xml
@@ -1,23 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright 2021, The Android Open Source Project
 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
 
-    http://www.apache.org/licenses/LICENSE-2.0
+      http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.theme">
-    <uses-sdk
-        android:minSdkVersion="24"
-        android:targetSdkVersion='24'/>
-</manifest>
+<!-- This file is intended for non translatable string resources. -->
+<resources>
+    <string name="flag_non_local_images_system_property_name" translatable="false">com.android.car.apps.common.FlagNonLocalImages</string>
+</resources>
diff --git a/car-apps-common/src/com/android/car/apps/common/CarControlBar.java b/car-apps-common/src/com/android/car/apps/common/CarControlBar.java
index 808d421..a84e12c 100644
--- a/car-apps-common/src/com/android/car/apps/common/CarControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/CarControlBar.java
@@ -56,6 +56,20 @@
     ImageButton createIconButton(Drawable icon, int viewId);
 
 
+    /** Constant for {@link #getFocusedViewIndex} and {@link #setFocusAtViewIndex}. */
+    int INVALID_VIEW_INDEX = -1;
+
+    /**
+     * Returns the index of the focused view, or {@link #INVALID_VIEW_INDEX} if no view is focused.
+     */
+    int getFocusedViewIndex();
+
+    /**
+     *  Sets the focus onto the view designated by the given index (typically obtained by
+     *  {@link #getFocusedViewIndex}.
+     */
+    void setFocusAtViewIndex(int viewIndex);
+
     @Retention(SOURCE)
     @IntDef({SLOT_MAIN, SLOT_LEFT, SLOT_RIGHT, SLOT_EXPAND_COLLAPSE})
     @interface SlotPosition {}
diff --git a/car-apps-common/src/com/android/car/apps/common/CommonFlags.java b/car-apps-common/src/com/android/car/apps/common/CommonFlags.java
index f70d296..84d30ee 100644
--- a/car-apps-common/src/com/android/car/apps/common/CommonFlags.java
+++ b/car-apps-common/src/com/android/car/apps/common/CommonFlags.java
@@ -19,16 +19,14 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.SystemProperties;
 
 import androidx.annotation.NonNull;
 
+import com.android.car.ui.utils.CarUiUtils;
+
 /** Singleton class regrouping common library feature flags. */
 public class CommonFlags {
 
-    private static final String FLAG_IMPROPER_IMAGE_REFS_KEY =
-            "com.android.car.apps.common.FlagNonLocalImages";
-
     @SuppressWarnings("StaticFieldLeak") // We store the application context, not an activity.
     private static CommonFlags sInstance;
 
@@ -68,7 +66,8 @@
         if (mFlagImproperImageRefs == null) {
             Resources res = mApplicationContext.getResources();
             mFlagImproperImageRefs = res.getBoolean(R.bool.flag_improper_image_references)
-                    || "1".equals(SystemProperties.get(FLAG_IMPROPER_IMAGE_REFS_KEY, "0"));
+                    || "1".equals(CarUiUtils.getSystemProperty(res,
+                    R.string.flag_non_local_images_system_property_name));
         }
         return mFlagImproperImageRefs;
     }
diff --git a/car-apps-common/src/com/android/car/apps/common/ControlBar.java b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
index 94fb206..900a01a 100644
--- a/car-apps-common/src/com/android/car/apps/common/ControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/ControlBar.java
@@ -240,6 +240,24 @@
     }
 
     @Override
+    public int getFocusedViewIndex() {
+        for (int slotIndex = 0; slotIndex < mSlots.length; slotIndex++) {
+            FrameLayout slot = mSlots[slotIndex];
+            if ((slot != null) && slot.hasFocus()) {
+                return slotIndex;
+            }
+        }
+        return INVALID_VIEW_INDEX;
+    }
+
+    @Override
+    public void setFocusAtViewIndex(int i) {
+        if ((INVALID_VIEW_INDEX < i) && (i < mSlots.length) && (mSlots[i] != null)) {
+            mSlots[i].requestFocus();
+        }
+    }
+
+    @Override
     public void registerExpandCollapseCallback(@Nullable ExpandCollapseCallback callback) {
         mExpandCollapseCallback = callback;
     }
diff --git a/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java b/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
index 2eeb1b8..54daf2f 100644
--- a/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
+++ b/car-apps-common/src/com/android/car/apps/common/MinimizedControlBar.java
@@ -16,6 +16,8 @@
 
 package com.android.car.apps.common;
 
+import static com.android.car.apps.common.ControlBar.INVALID_VIEW_INDEX;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -111,6 +113,24 @@
         return button;
     }
 
+    @Override
+    public int getFocusedViewIndex() {
+        for (int slotIndex = 0; slotIndex < mSlots.length; slotIndex++) {
+            FrameLayout slot = mSlots[slotIndex];
+            if ((slot != null) && slot.hasFocus()) {
+                return slotIndex;
+            }
+        }
+        return INVALID_VIEW_INDEX;
+    }
+
+    @Override
+    public void setFocusAtViewIndex(int i) {
+        if ((INVALID_VIEW_INDEX < i) && (i < mSlots.length) && (mSlots[i] != null)) {
+            mSlots[i].requestFocus();
+        }
+    }
+
     private void updateViewsLayout() {
         int viewIndex = 0;
         // Fill in slots with provided views
diff --git a/car-arch-common/src/com/android/car/arch/common/testing/CaptureObserver.java b/car-apps-common/src/com/android/car/apps/common/testutils/CaptureObserver.java
similarity index 94%
rename from car-arch-common/src/com/android/car/arch/common/testing/CaptureObserver.java
rename to car-apps-common/src/com/android/car/apps/common/testutils/CaptureObserver.java
index f553609..bee1c22 100644
--- a/car-arch-common/src/com/android/car/arch/common/testing/CaptureObserver.java
+++ b/car-apps-common/src/com/android/car/apps/common/testutils/CaptureObserver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.arch.common.testing;
+package com.android.car.apps.common.testutils;
 
 
 import androidx.annotation.Nullable;
diff --git a/car-arch-common/src/com/android/car/arch/common/testing/InstantTaskExecutorRule.java b/car-apps-common/src/com/android/car/apps/common/testutils/InstantTaskExecutorRule.java
similarity index 94%
rename from car-arch-common/src/com/android/car/arch/common/testing/InstantTaskExecutorRule.java
rename to car-apps-common/src/com/android/car/apps/common/testutils/InstantTaskExecutorRule.java
index c503bd4..2b49011 100644
--- a/car-arch-common/src/com/android/car/arch/common/testing/InstantTaskExecutorRule.java
+++ b/car-apps-common/src/com/android/car/apps/common/testutils/InstantTaskExecutorRule.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.arch.common.testing;
+package com.android.car.apps.common.testutils;
 
 import androidx.arch.core.executor.ArchTaskExecutor;
 import androidx.arch.core.executor.TaskExecutor;
diff --git a/car-arch-common/src/com/android/car/arch/common/testing/TestLifecycleOwner.java b/car-apps-common/src/com/android/car/apps/common/testutils/TestLifecycleOwner.java
similarity index 94%
rename from car-arch-common/src/com/android/car/arch/common/testing/TestLifecycleOwner.java
rename to car-apps-common/src/com/android/car/apps/common/testutils/TestLifecycleOwner.java
index c27b51e..e57e47e 100644
--- a/car-arch-common/src/com/android/car/arch/common/testing/TestLifecycleOwner.java
+++ b/car-apps-common/src/com/android/car/apps/common/testutils/TestLifecycleOwner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.arch.common.testing;
+package com.android.car.apps.common.testutils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
diff --git a/car-arch-common/src/com/android/car/arch/common/FutureData.java b/car-apps-common/src/com/android/car/apps/common/util/FutureData.java
similarity index 96%
rename from car-arch-common/src/com/android/car/arch/common/FutureData.java
rename to car-apps-common/src/com/android/car/apps/common/util/FutureData.java
index 8336736..a591fc8 100644
--- a/car-arch-common/src/com/android/car/arch/common/FutureData.java
+++ b/car-apps-common/src/com/android/car/apps/common/util/FutureData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2021 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.arch.common;
+package com.android.car.apps.common.util;
 
 import androidx.annotation.Nullable;
 
diff --git a/car-apps-common/src/com/android/car/apps/common/util/LiveDataFunctions.java b/car-apps-common/src/com/android/car/apps/common/util/LiveDataFunctions.java
new file mode 100644
index 0000000..51fa669
--- /dev/null
+++ b/car-apps-common/src/com/android/car/apps/common/util/LiveDataFunctions.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.apps.common.util;
+
+import static java.util.Objects.requireNonNull;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+import androidx.core.util.Pair;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
+
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+/**
+ * Utility methods for using {@link LiveData}. In general for Boolean operations, {@code null} is
+ * treated as an "unknown" value, and operations may use short-circuit evaluation to determine the
+ * result. LiveData may be in an uninitialized state where observers are not called when registered
+ * (e.g. a {@link MutableLiveData} where {@link MutableLiveData#setValue(Object)} has not yet been
+ * called). If a Boolean operation receives an uninitialized LiveData as either of its parameters,
+ * the result will also be in an uninitialized state.
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class LiveDataFunctions {
+
+    private LiveDataFunctions() {
+    }
+
+    private static volatile LiveData<?> sNullLiveData;
+    private static volatile LiveData<Boolean> sTrueLiveData;
+    private static volatile LiveData<Boolean> sFalseLiveData;
+
+    /**
+     * Returns a LiveData that always emits {@code null}. This is different than an uninitialized
+     * LiveData in that observers will be called (with {@code null}) when registered.
+     */
+    public static <T> LiveData<T> nullLiveData() {
+        if (sNullLiveData == null) {
+            sNullLiveData = dataOf(null);
+        }
+        // null can fit any generic type
+        // noinspection unchecked
+        return (LiveData<T>) sNullLiveData;
+    }
+
+    /** Returns a LiveData that is initialized with {@code value}. */
+    public static <T> MutableLiveData<T> dataOf(@Nullable T value) {
+        MutableLiveData<T> data = new MutableLiveData<>();
+        data.setValue(value);
+        return data;
+    }
+
+    /**
+     * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code null} when
+     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
+     */
+    public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source,
+            @NonNull Function<T, R> func) {
+        return mapNonNull(source, null, func);
+    }
+
+    /**
+     * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code nullValue} when
+     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
+     */
+    public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source, @Nullable R nullValue,
+            @NonNull Function<T, R> func) {
+        return Transformations.map(source, value -> value == null ? nullValue : func.apply(value));
+    }
+
+    /**
+     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code null} when
+     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
+     */
+    public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source,
+            @NonNull Function<T, LiveData<R>> func) {
+        return switchMapNonNull(source, null, func);
+    }
+
+    /**
+     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code nullValue}
+     * when {@code source} emits {@code null}. The input to {@code func} may be treated as not
+     * nullable.
+     */
+    public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source,
+            @Nullable R nullValue,
+            @NonNull Function<T, LiveData<R>> func) {
+        return Transformations.switchMap(source,
+                value -> value == null ? nullLiveData() : func.apply(value));
+    }
+
+    /**
+     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits a FutureData,
+     * which provides a loading field for operations which may take a long time to finish.
+     *
+     * This LiveData emits values only when the loading status of the output changes. It will never
+     * emit {@code null}. If the output is loading, the emitted FutureData will have a null value
+     * for the data.
+     */
+    public static <T, R> LiveData<FutureData<R>> loadingSwitchMap(LiveData<T> trigger,
+            @NonNull Function<T, LiveData<R>> func) {
+        LiveData<R> output = Transformations.switchMap(trigger, func);
+        return new MediatorLiveData<FutureData<R>>() {
+            {
+                addSource(trigger, data -> setValue(new FutureData<>(true, null)));
+                addSource(output, data ->
+                        setValue(new FutureData<>(false, output.getValue())));
+            }
+        };
+    }
+
+    /**
+     * Returns a LiveData backed by {@code value} if and only if predicate emits {@code true}. Emits
+     * {@code null} otherwise.
+     * <p>
+     * This is equivalent to {@code iff(predicate, Boolean::booleanValue, value)}
+     *
+     * @see #iff(LiveData, Predicate, LiveData)
+     */
+    public static <T> LiveData<T> iff(
+            @NonNull LiveData<Boolean> predicate, @NonNull LiveData<T> value) {
+        return iff(predicate, Boolean::booleanValue, value);
+    }
+
+    /**
+     * Returns a LiveData backed by {@code value} if and only if the trigger emits a value that
+     * causes {@code predicate} to return {@code true}. Emits {@code null} otherwise.
+     */
+    public static <P, T> LiveData<T> iff(
+            @NonNull LiveData<P> trigger,
+            @NonNull Predicate<? super P> predicate,
+            @NonNull LiveData<T> value) {
+        return new BinaryOperation<>(
+                trigger, value, (p, v) -> p == null || !predicate.test(p) ? null : v);
+    }
+
+    /**
+     * Returns a LiveData that emits a Pair containing the values of the two parameter LiveDatas. If
+     * either parameter is uninitialized, the resulting LiveData is also uninitialized.
+     * <p>
+     * This is equivalent to calling {@code combine(tData, uData, Pair::new)}.
+     *
+     * @see #combine(LiveData, LiveData, BiFunction)
+     */
+    public static <T, U> LiveData<Pair<T, U>> pair(
+            @NonNull LiveData<T> tData, @NonNull LiveData<U> uData) {
+        return combine(tData, uData, Pair::new);
+    }
+
+    /**
+     * Returns a LiveData that emits the result of {@code function} on the values of the two
+     * parameter LiveDatas. If either parameter is uninitialized, the resulting LiveData is also
+     * uninitialized.
+     */
+    public static <T, U, R> LiveData<R> combine(
+            @NonNull LiveData<T> tData,
+            @NonNull LiveData<U> uData,
+            @NonNull BiFunction<T, U, R> function) {
+        return new BinaryOperation<>(tData, uData, function);
+    }
+
+    private static class BinaryOperation<T, U, R> extends MediatorLiveData<R> {
+        @NonNull
+        private final BiFunction<T, U, R> mFunction;
+
+        private boolean mTSet;
+        private boolean mUSet;
+        private boolean mValueSet;
+
+        @Nullable
+        private T mTValue;
+        @Nullable
+        private U mUValue;
+
+        BinaryOperation(
+                @NonNull LiveData<T> tLiveData,
+                @NonNull LiveData<U> uLiveData,
+                @NonNull BiFunction<T, U, R> function) {
+            this(tLiveData, uLiveData, true, true, function);
+        }
+
+        BinaryOperation(
+                @NonNull LiveData<T> tLiveData,
+                @NonNull LiveData<U> uLiveData,
+                boolean requireTSet,
+                boolean requireUSet,
+                @NonNull BiFunction<T, U, R> function) {
+            this.mFunction = function;
+            if (!requireTSet) {
+                mTSet = true;
+            }
+            if (!requireUSet) {
+                mUSet = true;
+            }
+            if (tLiveData == uLiveData) {
+                // Only add the source once and only update once when it changes.
+                addSource(
+                        tLiveData,
+                        value -> {
+                            mTSet = true;
+                            mUSet = true;
+                            mTValue = value;
+                            // if both references point to the same LiveData, then T and U are
+                            // compatible types.
+                            // noinspection unchecked
+                            mUValue = (U) value;
+                            update();
+                        });
+            } else {
+                addSource(requireNonNull(tLiveData), this::updateT);
+                addSource(requireNonNull(uLiveData), this::updateU);
+            }
+        }
+
+        private void updateT(@Nullable T tValue) {
+            mTSet = true;
+            this.mTValue = tValue;
+            update();
+        }
+
+        private void updateU(@Nullable U uValue) {
+            mUSet = true;
+            this.mUValue = uValue;
+            update();
+        }
+
+        private void update() {
+            if (mTSet && mUSet) {
+                R result = mFunction.apply(mTValue, mUValue);
+                // Don't setValue if it's the same as the old value unless we haven't set a value
+                // before.
+                if (!mValueSet || result != getValue()) {
+                    mValueSet = true;
+                    setValue(result);
+                }
+            }
+        }
+    }
+}
diff --git a/car-apps-common/tests/unittests/Android.bp b/car-apps-common/tests/unittests/Android.bp
new file mode 100644
index 0000000..5cd8138
--- /dev/null
+++ b/car-apps-common/tests/unittests/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2021 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.
+//
+
+//############################################################
+// car-apps-common unit test target, run with:
+// mmma -j64 packages/apps/Car/libs/car-apps-common
+// atest car-apps-common-unit-tests
+//############################################################
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "car-apps-common-unit-tests",
+
+    min_sdk_version: "29",
+
+    target_sdk_version: "31",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.espresso.core",
+        "androidx.test.espresso.contrib",
+        "androidx.test.ext.junit",
+        "car-apps-common",
+        "platform-test-annotations",
+        "mockito-target-inline-minus-junit4",
+        "truth-prebuilt",
+    ],
+}
diff --git a/car-apps-common/tests/unittests/AndroidManifest.xml b/car-apps-common/tests/unittests/AndroidManifest.xml
new file mode 100644
index 0000000..53f7ce4
--- /dev/null
+++ b/car-apps-common/tests/unittests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2021 Google Inc.
+
+    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.apps.common.unittests">
+
+    <application android:testOnly="false"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.apps.common.unittests"
+                     android:label="Car apps common Unit Tests" />
+</manifest>
diff --git a/car-apps-common/tests/unittests/src/com/android/car/apps/common/LiveDataFunctionsTest.java b/car-apps-common/tests/unittests/src/com/android/car/apps/common/LiveDataFunctionsTest.java
new file mode 100644
index 0000000..43a6a8e
--- /dev/null
+++ b/car-apps-common/tests/unittests/src/com/android/car/apps/common/LiveDataFunctionsTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.apps.common;
+
+import static com.android.car.apps.common.util.LiveDataFunctions.dataOf;
+import static com.android.car.apps.common.util.LiveDataFunctions.nullLiveData;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.core.util.Pair;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.apps.common.testutils.CaptureObserver;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
+import com.android.car.apps.common.util.LiveDataFunctions;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+public class LiveDataFunctionsTest {
+
+    @Rule
+    public final InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
+    @Rule
+    public final TestLifecycleOwner mLifecycleOwner = new TestLifecycleOwner();
+
+    @Test
+    public void testNullLiveData() {
+        CaptureObserver<Object> observer = new CaptureObserver<>();
+        nullLiveData().observe(mLifecycleOwner, observer);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isNull();
+        assertThat(nullLiveData().getValue()).isNull();
+    }
+
+    @Test
+    public void testIff_truthTable() {
+        Object object = new Object();
+        testBinaryOperator(
+                (predicate, value) -> LiveDataFunctions.iff(predicate, Boolean::booleanValue,
+                        value),
+                pair(pair(dataOf(true), dataOf(object)), object),
+                pair(pair(dataOf(false), dataOf(object)), null),
+                pair(pair(nullLiveData(), dataOf(object)), null));
+    }
+
+    @Test
+    public void testIff_uninitialized() {
+        checkUninitializedBinary(
+                (predicate, value) -> LiveDataFunctions.iff(predicate, Boolean::booleanValue,
+                        value),
+                pair(new MutableLiveData<>(), dataOf(new Object())),
+                pair(dataOf(false), new MutableLiveData<>()),
+                pair(dataOf(true), new MutableLiveData<>()));
+    }
+
+    @Test
+    public void testIff_changePredicate() {
+        MutableLiveData<Boolean> predicate = new MutableLiveData<>();
+        MutableLiveData<Object> value = new MutableLiveData<>();
+        Object valueObject = new Object();
+        value.setValue(valueObject);
+        CaptureObserver<Object> observer = new CaptureObserver<>();
+
+        LiveDataFunctions.iff(predicate, Boolean::booleanValue, value)
+                .observe(mLifecycleOwner, observer);
+        assertThat(observer.hasBeenNotified()).isFalse();
+
+        predicate.setValue(false);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isNull();
+        observer.reset();
+
+        predicate.setValue(true);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isSameInstanceAs(valueObject);
+        observer.reset();
+
+        predicate.setValue(null);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isNull();
+    }
+
+    @Test
+    public void testIff_changeValue() {
+        LiveData<Boolean> predicate = dataOf(true);
+        MutableLiveData<Object> value = new MutableLiveData<>();
+        Object firstObject = new Object();
+        CaptureObserver<Object> observer = new CaptureObserver<>();
+
+        LiveDataFunctions.iff(predicate, Boolean::booleanValue, value)
+                .observe(mLifecycleOwner, observer);
+        assertThat(observer.hasBeenNotified()).isFalse();
+
+        value.setValue(null);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isNull();
+        observer.reset();
+
+        value.setValue(firstObject);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isSameInstanceAs(firstObject);
+        observer.reset();
+
+        value.setValue(new Object());
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isNotSameInstanceAs(firstObject);
+    }
+
+    @Test
+    public void testIff_changeValue_doesntNotifyForIrrelevantChanges() {
+        MutableLiveData<Object> irrelevantValue = new MutableLiveData<>();
+        irrelevantValue.setValue(new Object());
+        CaptureObserver<Object> observer = new CaptureObserver<>();
+
+        // irrelevantValue irrelevant because iff() always emits null when predicate is false.
+        LiveDataFunctions.iff(dataOf(false), Boolean::booleanValue, irrelevantValue)
+                .observe(mLifecycleOwner, observer);
+        assertThat(observer.hasBeenNotified()).isTrue();
+        observer.reset();
+
+        irrelevantValue.setValue(null);
+
+        assertThat(observer.hasBeenNotified()).isFalse();
+    }
+
+    @Test
+    public void testCombine() {
+        Object first = new Object();
+        Object second = new Object();
+
+        MutableLiveData<Object> firstData = new MutableLiveData<>();
+        MutableLiveData<Object> secondData = new MutableLiveData<>();
+        firstData.setValue(first);
+        secondData.setValue(second);
+
+        CaptureObserver<Pair<Object, Object>> observer = new CaptureObserver<>();
+        LiveDataFunctions.combine(firstData, secondData, Pair::new).observe(mLifecycleOwner,
+                observer);
+
+        Pair<Object, Object> observedValue = observer.getObservedValue();
+        assertThat(observedValue).isNotNull();
+        assertThat(observedValue.first).isSameInstanceAs(first);
+        assertThat(observedValue.second).isSameInstanceAs(second);
+
+        Object third = new Object();
+        firstData.setValue(third);
+
+        observedValue = observer.getObservedValue();
+        assertThat(observedValue).isNotNull();
+        assertThat(observedValue.first).isSameInstanceAs(third);
+        assertThat(observedValue.second).isSameInstanceAs(second);
+    }
+
+    private <R> void testOperator(Supplier<LiveData<R>> op, R result) {
+        CaptureObserver<R> observer = new CaptureObserver<>();
+        LiveData<R> data = op.get();
+
+        data.observe(mLifecycleOwner, observer);
+
+        assertThat(observer.hasBeenNotified()).isTrue();
+        assertThat(observer.getObservedValue()).isEqualTo(result);
+        assertThat(data.getValue()).isEqualTo(result);
+    }
+
+    @SafeVarargs // args are never written to
+    private final <P, R> void testOperator(
+            Function<P, Supplier<LiveData<R>>> ops, Pair<P, R>... args) {
+        for (Pair<P, R> arg : args) {
+            testOperator(ops.apply(arg.first), arg.second);
+        }
+    }
+
+    @SafeVarargs // args are never written to
+    private final <T, R> void testUnaryOperator(
+            Function<LiveData<T>, LiveData<R>> op, Pair<LiveData<T>, R>... args) {
+        testOperator(arg -> () -> op.apply(arg), args);
+    }
+
+    @SafeVarargs // args are never written to
+    private final <A, B, R> void testBinaryOperator(
+            BiFunction<LiveData<A>, LiveData<B>, LiveData<R>> op,
+            Pair<Pair<LiveData<A>, LiveData<B>>, R>... args) {
+        testOperator(arg -> () -> op.apply(arg.first, arg.second), args);
+    }
+
+    private <T, R> Pair<T, R> pair(T first, R second) {
+        return new Pair<>(first, second);
+    }
+
+    private <T> void checkUninitialized(LiveData<T> liveData) {
+        CaptureObserver<T> observer = new CaptureObserver<>();
+
+        liveData.observe(mLifecycleOwner, observer);
+
+        assertThat(observer.hasBeenNotified()).isFalse();
+        assertThat(liveData.getValue()).isNull();
+    }
+
+    @SafeVarargs // args are never written to
+    private final <A, B> void checkUninitializedBinary(
+            BiFunction<LiveData<A>, LiveData<B>, LiveData<?>> op,
+            Pair<LiveData<A>, LiveData<B>>... args) {
+        for (Pair<LiveData<A>, LiveData<B>> arg : args) {
+            checkUninitialized(op.apply(arg.first, arg.second));
+        }
+    }
+}
diff --git a/car-arch-common/tests/robotests/src/com/android/car/arch/common/LoadingSwitchMapTest.java b/car-apps-common/tests/unittests/src/com/android/car/apps/common/LoadingSwitchMapTest.java
similarity index 89%
rename from car-arch-common/tests/robotests/src/com/android/car/arch/common/LoadingSwitchMapTest.java
rename to car-apps-common/tests/unittests/src/com/android/car/apps/common/LoadingSwitchMapTest.java
index d64a0c1..6dab039 100644
--- a/car-arch-common/tests/robotests/src/com/android/car/arch/common/LoadingSwitchMapTest.java
+++ b/car-apps-common/tests/unittests/src/com/android/car/apps/common/LoadingSwitchMapTest.java
@@ -14,26 +14,27 @@
  * limitations under the License.
  */
 
-package com.android.car.arch.common;
+package com.android.car.apps.common;
 
-import static com.android.car.arch.common.LiveDataFunctions.loadingSwitchMap;
+import static com.android.car.apps.common.util.LiveDataFunctions.loadingSwitchMap;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.car.arch.common.testing.CaptureObserver;
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.CaptureObserver;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
+import com.android.car.apps.common.util.FutureData;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class LoadingSwitchMapTest {
 
     @Rule
diff --git a/car-arch-common/Android.bp b/car-arch-common/Android.bp
deleted file mode 100644
index 06e7923..0000000
--- a/car-arch-common/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Copyright (C) 2018 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.
-//
-
-// This is an unbundled target, sdk_version must be "system_current". If the car library is ever
-// needed, android."car-stubs" or "android.car-system-stubs" must be used.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "car-arch-common",
-
-    srcs: ["src/**/*.java"],
-
-    optimize: {
-        enabled: false,
-    },
-
-    sdk_version: "system_current",
-
-    static_libs: [
-        "androidx.lifecycle_lifecycle-extensions",
-        "androidx.lifecycle_lifecycle-common-java8",
-        "androidx.annotation_annotation",
-        "junit",
-    ],
-}
diff --git a/car-arch-common/AndroidManifest.xml b/car-arch-common/AndroidManifest.xml
deleted file mode 100644
index 638f0f2..0000000
--- a/car-arch-common/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2018 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.arch.common">
-</manifest>
diff --git a/car-arch-common/OWNERS b/car-arch-common/OWNERS
deleted file mode 100644
index 8c04328..0000000
--- a/car-arch-common/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# People who can approve changes for submission.
-keyboardr@google.com
diff --git a/car-arch-common/src/com/android/car/arch/common/LiveDataFunctions.java b/car-arch-common/src/com/android/car/arch/common/LiveDataFunctions.java
deleted file mode 100644
index 1fc4ff1..0000000
--- a/car-arch-common/src/com/android/car/arch/common/LiveDataFunctions.java
+++ /dev/null
@@ -1,637 +0,0 @@
-/*
- * Copyright 2018 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.arch.common;
-
-import static java.util.Objects.requireNonNull;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-import androidx.core.util.Pair;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.Transformations;
-
-import java.util.Objects;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-
-/**
- * Utility methods for using {@link LiveData}. In general for Boolean operations, {@code null} is
- * treated as an "unknown" value, and operations may use short-circuit evaluation to determine the
- * result. LiveData may be in an uninitialized state where observers are not called when registered
- * (e.g. a {@link MutableLiveData} where {@link MutableLiveData#setValue(Object)} has not yet been
- * called). If a Boolean operation receives an uninitialized LiveData as either of its parameters,
- * the result will also be in an uninitialized state.
- */
-@SuppressWarnings({"unused", "WeakerAccess"})
-public class LiveDataFunctions {
-
-    private LiveDataFunctions() {
-    }
-
-    private static volatile LiveData<?> sNullLiveData;
-    private static volatile LiveData<Boolean> sTrueLiveData;
-    private static volatile LiveData<Boolean> sFalseLiveData;
-
-    /**
-     * Returns a LiveData that always emits {@code null}. This is different than an uninitialized
-     * LiveData in that observers will be called (with {@code null}) when registered.
-     */
-    public static <T> LiveData<T> nullLiveData() {
-        if (sNullLiveData == null) {
-            sNullLiveData = dataOf(null);
-        }
-        // null can fit any generic type
-        // noinspection unchecked
-        return (LiveData<T>) sNullLiveData;
-    }
-
-    /** Returns a LiveData that always emits {@code true}. */
-    public static LiveData<Boolean> trueLiveData() {
-        if (sTrueLiveData == null) {
-            sTrueLiveData = dataOf(true);
-        }
-        return sTrueLiveData;
-    }
-
-    /** Returns a LiveData that always emits {@code false}. */
-    public static LiveData<Boolean> falseLiveData() {
-        if (sFalseLiveData == null) {
-            sFalseLiveData = dataOf(false);
-        }
-        return sFalseLiveData;
-    }
-
-    /** Returns a LiveData that is initialized with {@code value}. */
-    public static <T> MutableLiveData<T> dataOf(@Nullable T value) {
-        MutableLiveData<T> data = new MutableLiveData<>();
-        data.setValue(value);
-        return data;
-    }
-
-    /**
-     * Returns a LiveData that emits the opposite of {@code source} (or {@code null} if {@code
-     * source} emits {@code null})
-     */
-    public static LiveData<Boolean> not(@NonNull LiveData<Boolean> source) {
-        return Transformations.map(source, bool -> bool == null ? null : !bool);
-    }
-
-    /**
-     * Returns a LiveData that emits {@code true} iff {@code source} emits {@code null}. Otherwise
-     * emits {@code false}
-     */
-    public static LiveData<Boolean> emitsNull(@NonNull LiveData<?> source) {
-        return Transformations.map(source, Objects::isNull);
-    }
-
-    /**
-     * Returns a LiveData that emits the same value as {@code source}, but only notifies its
-     * observers when the new value is distinct ({@link Objects#equals(Object, Object)}
-     */
-    public static <T> LiveData<T> distinct(@NonNull LiveData<T> source) {
-        return distinct(source, Objects::equals);
-    }
-
-    /**
-     * Returns a LiveData that emits the same value as {@code source}, but only notifies its
-     * observers when the new value is distinct ({@code areEqual} returns {@code false})
-     */
-    public static <T> LiveData<T> distinct(@NonNull LiveData<T> source,
-            @NonNull BiPredicate<T, T> areEqual) {
-        return new MediatorLiveData<T>() {
-            private boolean mInitialized = false;
-
-            {
-                addSource(source, value -> {
-                    if (!mInitialized || !areEqual.test(value, getValue())) {
-                        mInitialized = true;
-                        setValue(value);
-                    }
-                });
-            }
-        };
-    }
-
-    /**
-     * Create a LiveData that doesn't change when {@code isFrozen} emits {@code true}. If {@code
-     * source} has updated while the data was frozen, it will be updated to the current value once
-     * unfrozen.
-     *
-     * @param isFrozen the result will not update while this data emits {@code true}.
-     * @param source   the source data for the result.
-     * @return a LiveData that doesn't change when {@code isFrozen} emits {@code true}.
-     */
-    public static <T> LiveData<T> freezable(@NonNull LiveData<Boolean> isFrozen,
-            @NonNull LiveData<T> source) {
-        return new MediatorLiveData<T>() {
-
-            private boolean mDirty = false;
-
-            {
-                addSource(requireNonNull(isFrozen), frozen -> {
-                    if (frozen == Boolean.FALSE && mDirty) {
-                        setValue(source.getValue());
-                        mDirty = false;
-                    }
-                });
-                addSource(requireNonNull(source), value -> {
-                    if (isFrozen.getValue() != Boolean.FALSE) {
-                        mDirty = true;
-                    } else {
-                        setValue(source.getValue());
-                        mDirty = false;
-                    }
-                });
-            }
-        };
-    }
-
-    /**
-     * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code null} when
-     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
-     */
-    public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source,
-            @NonNull Function<T, R> func) {
-        return mapNonNull(source, null, func);
-    }
-
-    /**
-     * Similar to {@link Transformations#map(LiveData, Function)}, but emits {@code nullValue} when
-     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
-     */
-    public static <T, R> LiveData<R> mapNonNull(@NonNull LiveData<T> source, @Nullable R nullValue,
-            @NonNull Function<T, R> func) {
-        return Transformations.map(source, value -> value == null ? nullValue : func.apply(value));
-    }
-
-    /**
-     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code null} when
-     * {@code source} emits {@code null}. The input to {@code func} may be treated as not nullable.
-     */
-    public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source,
-            @NonNull Function<T, LiveData<R>> func) {
-        return switchMapNonNull(source, null, func);
-    }
-
-    /**
-     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits {@code nullValue}
-     * when {@code source} emits {@code null}. The input to {@code func} may be treated as not
-     * nullable.
-     */
-    public static <T, R> LiveData<R> switchMapNonNull(@NonNull LiveData<T> source,
-            @Nullable R nullValue,
-            @NonNull Function<T, LiveData<R>> func) {
-        return Transformations.switchMap(source,
-                value -> value == null ? nullLiveData() : func.apply(value));
-    }
-
-    /**
-     * Similar to {@link Transformations#switchMap(LiveData, Function)}, but emits a FutureData,
-     * which provides a loading field for operations which may take a long time to finish.
-     *
-     * This LiveData emits values only when the loading status of the output changes. It will never
-     * emit {@code null}. If the output is loading, the emitted FutureData will have a null value
-     * for the data.
-     */
-    public static <T, R> LiveData<FutureData<R>> loadingSwitchMap(LiveData<T> trigger,
-            @NonNull Function<T, LiveData<R>> func) {
-        LiveData<R> output = Transformations.switchMap(trigger, func);
-        return new MediatorLiveData<FutureData<R>>() {
-            {
-                addSource(trigger, data -> setValue(new FutureData<>(true, null)));
-                addSource(output, data ->
-                        setValue(new FutureData<>(false, output.getValue())));
-            }
-        };
-    }
-
-    /**
-     * Returns a LiveData that emits the logical AND of the two arguments. Also deals with {@code
-     * null} and uninitalized values as follows:
-     * <table>
-     * <tr>
-     * <th></th>
-     * <th>T</th>
-     * <th>F</th>
-     * <th>N</th>
-     * <th>U</th>
-     * </tr>
-     * <tr>
-     * <th>T</th>
-     * <td>T</td>
-     * <td>F</td>
-     * <td>N</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>F</th>
-     * <td>F</td>
-     * <td>F</td>
-     * <td>F</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>N</th>
-     * <td>N</td>
-     * <td>F</td>
-     * <td>N</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>U</th>
-     * <td>U</td>
-     * <td>U</td>
-     * <td>U</td>
-     * <td>U</td>
-     * </tr>
-     * </table>
-     * <p>
-     * T = {@code true}, F = {@code false}, N = {@code null}, U = uninitialized
-     *
-     * @return a LiveData that emits the logical AND of the two arguments
-     */
-    public static LiveData<Boolean> and(@NonNull LiveData<Boolean> x,
-            @NonNull LiveData<Boolean> y) {
-        return new BinaryOperation<>(
-                x,
-                y,
-                (a, b) -> {
-                    if (a == null) {
-                        if (Boolean.FALSE.equals(b)) {
-                            return false;
-                        }
-                        return null;
-                    }
-                    if (a) {
-                        return b;
-                    }
-                    return false;
-                });
-    }
-
-    /**
-     * Returns a LiveData that emits the logical OR of the two arguments. Also deals with {@code
-     * null} and uninitalized values as follows:
-     * <table>
-     * <tr>
-     * <th></th>
-     * <th>T</th>
-     * <th>F</th>
-     * <th>N</th>
-     * <th>U</th>
-     * </tr>
-     * <tr>
-     * <th>T</th>
-     * <td>T</td>
-     * <td>T</td>
-     * <td>T</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>F</th>
-     * <td>T</td>
-     * <td>F</td>
-     * <td>N</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>N</th>
-     * <td>T</td>
-     * <td>N</td>
-     * <td>N</td>
-     * <td>U</td>
-     * </tr>
-     * <tr>
-     * <th>U</th>
-     * <td>U</td>
-     * <td>U</td>
-     * <td>U</td>
-     * <td>U</td>
-     * </tr>
-     * </table>
-     * <p>
-     * T = {@code true}, F = {@code false}, N = {@code null}, U = uninitialized
-     *
-     * @return a LiveData that emits the logical OR of the two arguments
-     */
-    public static LiveData<Boolean> or(@NonNull LiveData<Boolean> x, @NonNull LiveData<Boolean> y) {
-        return new BinaryOperation<>(
-                x,
-                y,
-                (a, b) -> {
-                    if (a == null) {
-                        if (Boolean.TRUE.equals(b)) {
-                            return true;
-                        }
-                        return null;
-                    }
-                    if (!a) {
-                        return b;
-                    }
-                    return true;
-                });
-    }
-
-    /**
-     * Returns a LiveData backed by {@code value} if and only if predicate emits {@code true}. Emits
-     * {@code null} otherwise.
-     * <p>
-     * This is equivalent to {@code iff(predicate, Boolean::booleanValue, value)}
-     *
-     * @see #iff(LiveData, Predicate, LiveData)
-     */
-    public static <T> LiveData<T> iff(
-            @NonNull LiveData<Boolean> predicate, @NonNull LiveData<T> value) {
-        return iff(predicate, Boolean::booleanValue, value);
-    }
-
-    /**
-     * Returns a LiveData backed by {@code value} if and only if the trigger emits a value that
-     * causes {@code predicate} to return {@code true}. Emits {@code null} otherwise.
-     */
-    public static <P, T> LiveData<T> iff(
-            @NonNull LiveData<P> trigger,
-            @NonNull Predicate<? super P> predicate,
-            @NonNull LiveData<T> value) {
-        return new BinaryOperation<>(
-                trigger, value, (p, v) -> p == null || !predicate.test(p) ? null : v);
-    }
-
-    /**
-     * Returns a LiveData that is backed by {@code trueData} when the predicate emits {@code true},
-     * {@code falseData} when the predicate emits {@code false}, and emits {@code null} when the
-     * predicate emits {@code null}.
-     * <p>
-     * This is equivalent to {@code ifThenElse(predicate, Boolean::booleanValue, trueData,
-     * falseData)}
-     *
-     * @param trueData  the LiveData whose value should be emitted when predicate is {@code true}
-     * @param falseData the LiveData whose value should be emitted when predicate is {@code false}
-     * @see #ifThenElse(LiveData, Predicate, LiveData, LiveData)
-     */
-    public static <T> LiveData<T> ifThenElse(
-            @NonNull LiveData<Boolean> predicate,
-            @NonNull LiveData<T> trueData,
-            @NonNull LiveData<T> falseData) {
-        return ifThenElse(predicate, Boolean::booleanValue, trueData, falseData);
-    }
-
-    /**
-     * Returns a LiveData that is backed by {@code trueData} when the trigger satisfies the
-     * predicate, {@code falseData} when the trigger does not satisfy the predicate, and emits
-     * {@code null} when the trigger emits {@code null}.
-     *
-     * @param trueData  the LiveData whose value should be emitted when predicate returns {@code
-     *                  true}
-     * @param falseData the LiveData whose value should be emitted when predicate returns {@code
-     *                  false}
-     */
-    public static <P, T> LiveData<T> ifThenElse(
-            @NonNull LiveData<P> trigger,
-            @NonNull Predicate<? super P> predicate,
-            @NonNull LiveData<T> trueData,
-            @NonNull LiveData<T> falseData) {
-        return Transformations.switchMap(
-                trigger,
-                t -> {
-                    if (t == null) {
-                        return nullLiveData();
-                    } else {
-                        return predicate.test(t) ? trueData : falseData;
-                    }
-                });
-    }
-
-    /**
-     * Returns a LiveData that emits {@code trueValue} when the predicate emits {@code true}, {@code
-     * falseValue} when the predicate emits {@code false}, and emits {@code null} when the predicate
-     * emits {@code null}.
-     * <p>
-     * This is equivalent to {@code ifThenElse(predicate, Boolean::booleanValue, trueValue,
-     * falseValue)}
-     *
-     * @param trueValue  the value that should be emitted when predicate returns {@code true}
-     * @param falseValue the value that should be emitted when predicate returns {@code false}
-     * @see #ifThenElse(LiveData, Predicate, Object, Object)
-     */
-    public static <T> LiveData<T> ifThenElse(
-            @NonNull LiveData<Boolean> predicate, @Nullable T trueValue, @Nullable T falseValue) {
-        return ifThenElse(predicate, Boolean::booleanValue, trueValue, falseValue);
-    }
-
-    /**
-     * Returns a LiveData that emits {@code trueValue} when the trigger satisfies the predicate,
-     * {@code falseValue} when the trigger does not satisfy the predicate, and emits {@code null}
-     * when the trigger emits {@code null}.
-     *
-     * @param trueValue  the value that should be emitted when predicate returns {@code true}
-     * @param falseValue the value that should be emitted when predicate returns {@code false}
-     */
-    public static <P, T> LiveData<T> ifThenElse(
-            @NonNull LiveData<P> trigger,
-            @NonNull Predicate<? super P> predicate,
-            @Nullable T trueValue,
-            @Nullable T falseValue) {
-        return Transformations.map(
-                trigger,
-                t -> {
-                    if (t == null) {
-                        return null;
-                    }
-                    return predicate.test(t) ? trueValue : falseValue;
-                });
-    }
-
-    /**
-     * Returns a LiveData that emits the value of {@code source} if it is not {@code null},
-     * otherwise it emits the value of {@code fallback}.
-     *
-     * @param source   The LiveData whose value should be emitted if not {@code null}
-     * @param fallback The LiveData whose value should be emitted when {@code source} emits {@code
-     *                 null}
-     */
-    public static <T> LiveData<T> coalesceNull(@NonNull LiveData<T> source,
-            @NonNull LiveData<T> fallback) {
-        return new BinaryOperation<>(source, fallback, true, false,
-                (sourceValue, fallbackValue) -> sourceValue == null ? fallbackValue : sourceValue);
-    }
-
-    /**
-     * Returns a LiveData that emits the value of {@code source} if it is not {@code null},
-     * otherwise it emits {@code fallback}.
-     *
-     * @param source   The LiveData whose value should be emitted if not {@code null}
-     * @param fallback The value that should be emitted when {@code source} emits {@code null}
-     */
-    public static <T> LiveData<T> coalesceNull(@NonNull LiveData<T> source, T fallback) {
-        return Transformations.map(source, value -> value == null ? fallback : value);
-    }
-
-    /**
-     * Returns a LiveData that emits a Pair containing the values of the two parameter LiveDatas. If
-     * either parameter is uninitialized, the resulting LiveData is also uninitialized.
-     * <p>
-     * This is equivalent to calling {@code combine(tData, uData, Pair::new)}.
-     *
-     * @see #combine(LiveData, LiveData, BiFunction)
-     */
-    public static <T, U> LiveData<Pair<T, U>> pair(
-            @NonNull LiveData<T> tData, @NonNull LiveData<U> uData) {
-        return combine(tData, uData, Pair::new);
-    }
-
-    /**
-     * Returns an observer that splits a pair into two separate arguments. This method is mainly
-     * used to simplify lambda expressions and enable method references, especially in combination
-     * with {@link #pair(LiveData, LiveData)}.
-     * <p>
-     * Example:
-     * <pre><code>
-     * class MyViewModel extends ViewModel {
-     *   LiveData&lt;Integer> getIntData() {...}
-     *   LiveData&lt;Boolean> getBoolData() {...}
-     * }
-     *
-     * void consume(int intValue, boolean booleanValue) {...}
-     *
-     * void startObserving(MyViewModel viewModel) {
-     *   pair(viewModel.getIntData(), viewModel.getBoolData()).observe(owner, split(this::consume));
-     * }</code></pre>
-     */
-    public static <T, U> Observer<Pair<T, U>> split(@NonNull BiConsumer<T, U> consumer) {
-        return (pair) -> {
-            if (pair == null) {
-                consumer.accept(null, null);
-            } else {
-                consumer.accept(pair.first, pair.second);
-            }
-        };
-    }
-
-    /**
-     * Returns a switch Function that splits a pair into two separate arguments. This method is
-     * mainly used to simplify lambda expressions and enable method references for {@link
-     * Transformations#switchMap(LiveData, Function) switchMaps}, especially in combination with
-     * {@link #pair(LiveData, LiveData)}.
-     */
-    public static <T, U, V> Function<Pair<T, U>, LiveData<V>> split(
-            @NonNull BiFunction<T, U, LiveData<V>> function) {
-        return (pair) -> {
-            if (pair == null) {
-                return function.apply(null, null);
-            } else {
-                return function.apply(pair.first, pair.second);
-            }
-        };
-    }
-
-    /**
-     * Returns a LiveData that emits the result of {@code function} on the values of the two
-     * parameter LiveDatas. If either parameter is uninitialized, the resulting LiveData is also
-     * uninitialized.
-     */
-    public static <T, U, R> LiveData<R> combine(
-            @NonNull LiveData<T> tData,
-            @NonNull LiveData<U> uData,
-            @NonNull BiFunction<T, U, R> function) {
-        return new BinaryOperation<>(tData, uData, function);
-    }
-
-    private static class BinaryOperation<T, U, R> extends MediatorLiveData<R> {
-        @NonNull
-        private final BiFunction<T, U, R> mFunction;
-
-        private boolean mTSet;
-        private boolean mUSet;
-        private boolean mValueSet;
-
-        @Nullable
-        private T mTValue;
-        @Nullable
-        private U mUValue;
-
-        BinaryOperation(
-                @NonNull LiveData<T> tLiveData,
-                @NonNull LiveData<U> uLiveData,
-                @NonNull BiFunction<T, U, R> function) {
-            this(tLiveData, uLiveData, true, true, function);
-        }
-
-        BinaryOperation(
-                @NonNull LiveData<T> tLiveData,
-                @NonNull LiveData<U> uLiveData,
-                boolean requireTSet,
-                boolean requireUSet,
-                @NonNull BiFunction<T, U, R> function) {
-            this.mFunction = function;
-            if (!requireTSet) {
-                mTSet = true;
-            }
-            if (!requireUSet) {
-                mUSet = true;
-            }
-            if (tLiveData == uLiveData) {
-                // Only add the source once and only update once when it changes.
-                addSource(
-                        tLiveData,
-                        value -> {
-                            mTSet = true;
-                            mUSet = true;
-                            mTValue = value;
-                            // if both references point to the same LiveData, then T and U are
-                            // compatible types.
-                            // noinspection unchecked
-                            mUValue = (U) value;
-                            update();
-                        });
-            } else {
-                addSource(requireNonNull(tLiveData), this::updateT);
-                addSource(requireNonNull(uLiveData), this::updateU);
-            }
-        }
-
-        private void updateT(@Nullable T tValue) {
-            mTSet = true;
-            this.mTValue = tValue;
-            update();
-        }
-
-        private void updateU(@Nullable U uValue) {
-            mUSet = true;
-            this.mUValue = uValue;
-            update();
-        }
-
-        private void update() {
-            if (mTSet && mUSet) {
-                R result = mFunction.apply(mTValue, mUValue);
-                // Don't setValue if it's the same as the old value unless we haven't set a value
-                // before.
-                if (!mValueSet || result != getValue()) {
-                    mValueSet = true;
-                    setValue(result);
-                }
-            }
-        }
-    }
-}
diff --git a/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveData.java b/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveData.java
deleted file mode 100644
index 7dceb08..0000000
--- a/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveData.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2018 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.arch.common.switching;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-
-/**
- * Interface for a LiveData that emits the value of a given source. The LiveData can change sources
- * at any time, and the value emitted will represent only the most recent source. Older sources will
- * be forgotten.
- *
- * @param <T> The type this SwitchingLiveData will emit.
- */
-public interface SwitchingLiveData<T> {
-
-    /**
-     * Returns this SwitchingLiveData as a LiveData. This method is needed due to limitations in
-     * Java syntax.
-     */
-    @NonNull
-    LiveData<T> asLiveData();
-
-    /**
-     * Returns the current source as set by {@link #setSource(LiveData)}
-     */
-    @Nullable
-    LiveData<? extends T> getSource();
-
-    /**
-     * Sets which LiveData acts as the source for this SwitchingLiveData. If {@code null}, this
-     * SwitchingLiveData will emit {@code null}.
-     */
-    void setSource(@Nullable LiveData<? extends T> source);
-
-    /** Returns a new instance of SwitchingLiveData */
-    static <T> SwitchingLiveData<T> newInstance() {
-        return new SwitchingLiveDataImpl<>();
-    }
-}
diff --git a/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveDataImpl.java b/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveDataImpl.java
deleted file mode 100644
index 65d3502..0000000
--- a/car-arch-common/src/com/android/car/arch/common/switching/SwitchingLiveDataImpl.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2018 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.arch.common.switching;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MediatorLiveData;
-
-/**
- * Provides the implementation of {@link SwitchingLiveData}. This class uses an interface rather
- * than being exposed directly to ensure that its superclass {@link MediatorLiveData} is not
- * exposed. The use of MediatorLiveData is an implementation detail.
- */
-class SwitchingLiveDataImpl<T> extends MediatorLiveData<T> implements SwitchingLiveData<T> {
-    private LiveData<? extends T> mCurrentSource;
-
-    @NonNull
-    @Override
-    public LiveData<T> asLiveData() {
-        return this;
-    }
-
-    @Nullable
-    @Override
-    public LiveData<? extends T> getSource() {
-        return mCurrentSource;
-    }
-
-    public void setSource(@Nullable LiveData<? extends T> source) {
-        if (source == mCurrentSource) {
-            return;
-        }
-        if (mCurrentSource != null) {
-            removeSource(mCurrentSource);
-        }
-        mCurrentSource = source;
-        if (source != null) {
-            addSource(source, this::setValue);
-        } else {
-            setValue(null);
-        }
-    }
-}
diff --git a/car-arch-common/tests/robotests/Android.bp b/car-arch-common/tests/robotests/Android.bp
deleted file mode 100644
index ff4543c..0000000
--- a/car-arch-common/tests/robotests/Android.bp
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// Copyright (C) 2018 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.
-//
-
-//###########################################################
-// CarArchCommon app just for Robolectric test target.     #
-//###########################################################
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
-    name: "CarArchCommon",
-
-    platform_apis: true,
-
-    privileged: true,
-
-    static_libs: ["car-arch-common"],
-}
-
-//###############################################
-// Car Arch Common Robolectric test target. #
-//###############################################
-android_robolectric_test {
-    name: "CarArchCommonRoboTests",
-
-    srcs: ["src/**/*.java"],
-
-    java_resource_dirs: ["config"],
-
-    // Include the testing libraries
-    libs: [
-        "androidx.arch.core_core-runtime",
-        "androidx.arch.core_core-common",
-    ],
-
-    instrumentation_for: "CarArchCommon",
-}
diff --git a/car-arch-common/tests/robotests/AndroidManifest.xml b/car-arch-common/tests/robotests/AndroidManifest.xml
deleted file mode 100644
index 503ba3e..0000000
--- a/car-arch-common/tests/robotests/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 Google Inc.
-
-    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.arch.common.robotests">
-
-</manifest>
diff --git a/car-arch-common/tests/robotests/config/robolectric.properties b/car-arch-common/tests/robotests/config/robolectric.properties
deleted file mode 100644
index 4c863dc..0000000
--- a/car-arch-common/tests/robotests/config/robolectric.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2018 Google Inc.
-#
-# 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.
-#
-sdk=NEWEST_SDK
diff --git a/car-arch-common/tests/robotests/src/com/android/car/arch/common/LiveDataFunctionsTest.java b/car-arch-common/tests/robotests/src/com/android/car/arch/common/LiveDataFunctionsTest.java
deleted file mode 100644
index 2ab8943..0000000
--- a/car-arch-common/tests/robotests/src/com/android/car/arch/common/LiveDataFunctionsTest.java
+++ /dev/null
@@ -1,871 +0,0 @@
-/*
- * Copyright 2018 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.arch.common;
-
-import static com.android.car.arch.common.LiveDataFunctions.coalesceNull;
-import static com.android.car.arch.common.LiveDataFunctions.dataOf;
-import static com.android.car.arch.common.LiveDataFunctions.distinct;
-import static com.android.car.arch.common.LiveDataFunctions.emitsNull;
-import static com.android.car.arch.common.LiveDataFunctions.falseLiveData;
-import static com.android.car.arch.common.LiveDataFunctions.freezable;
-import static com.android.car.arch.common.LiveDataFunctions.ifThenElse;
-import static com.android.car.arch.common.LiveDataFunctions.not;
-import static com.android.car.arch.common.LiveDataFunctions.nullLiveData;
-import static com.android.car.arch.common.LiveDataFunctions.split;
-import static com.android.car.arch.common.LiveDataFunctions.trueLiveData;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.core.util.Pair;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.car.arch.common.testing.CaptureObserver;
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Objects;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-@RunWith(RobolectricTestRunner.class)
-public class LiveDataFunctionsTest {
-
-    @Rule
-    public final InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
-    @Rule
-    public final TestLifecycleOwner mLifecycleOwner = new TestLifecycleOwner();
-
-    @Test
-    public void testNullLiveData() {
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-        nullLiveData().observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-        assertThat(nullLiveData().getValue()).isNull();
-    }
-
-    @Test
-    public void testTrueLiveData() {
-        CaptureObserver<Boolean> observer = new CaptureObserver<>();
-        trueLiveData().observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isTrue();
-        assertThat(trueLiveData().getValue()).isTrue();
-    }
-
-    @Test
-    public void testFalseLiveData() {
-        CaptureObserver<Boolean> observer = new CaptureObserver<>();
-        falseLiveData().observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isFalse();
-        assertThat(falseLiveData().getValue()).isFalse();
-    }
-
-    @Test
-    public void testNot() {
-        testUnaryOperator(
-                LiveDataFunctions::not,
-                pair(trueLiveData(), false),
-                pair(falseLiveData(), true),
-                pair(nullLiveData(), null));
-
-        checkUninitialized(not(new MutableLiveData<>()));
-    }
-
-    @Test
-    public void testEmitsNull() {
-        testUnaryOperator(
-                LiveDataFunctions::emitsNull,
-                pair(dataOf(new Object()), false),
-                pair(nullLiveData(), true));
-        checkUninitialized(emitsNull(new MutableLiveData<>()));
-    }
-
-    @Test
-    public void testDistinct() {
-        CaptureObserver<Integer> observer = new CaptureObserver<>();
-        MutableLiveData<Integer> source = dataOf(0);
-        LiveData<Integer> distinct = distinct(source);
-        distinct.observe(mLifecycleOwner, observer);
-        observer.reset();
-
-        source.setValue(1);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(1);
-        observer.reset();
-
-        source.setValue(1);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        source.setValue(2);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(2);
-    }
-
-    @Test
-    public void testFreezable() {
-        CaptureObserver<Integer> observer = new CaptureObserver<>();
-        MutableLiveData<Boolean> isFrozen = dataOf(false);
-        MutableLiveData<Integer> source = dataOf(0);
-        LiveData<Integer> freezable = freezable(isFrozen, source);
-        freezable.observe(mLifecycleOwner, observer);
-
-        // Initialized to correct value.
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(0);
-        observer.reset();
-
-        // Updates with source when not frozen.
-        source.setValue(1);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(1);
-        observer.reset();
-
-        // Doesn't update when frozen.
-        isFrozen.setValue(true);
-        source.setValue(2);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        // Updates when unfrozen.
-        isFrozen.setValue(false);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(2);
-        observer.reset();
-
-        // Doesn't notify if no changes while frozen.
-        isFrozen.setValue(true);
-        isFrozen.setValue(false);
-        assertThat(observer.hasBeenNotified()).isFalse();
-    }
-
-    @Test
-    public void testAnd_truthTable() {
-        testBinaryOperator(
-                LiveDataFunctions::and,
-                pair(pair(trueLiveData(), trueLiveData()), true),
-                pair(pair(trueLiveData(), falseLiveData()), false),
-                pair(pair(trueLiveData(), nullLiveData()), null),
-                pair(pair(falseLiveData(), trueLiveData()), false),
-                pair(pair(falseLiveData(), falseLiveData()), false),
-                pair(pair(falseLiveData(), nullLiveData()), false),
-                pair(pair(nullLiveData(), trueLiveData()), null),
-                pair(pair(nullLiveData(), falseLiveData()), false),
-                pair(pair(nullLiveData(), nullLiveData()), null));
-    }
-
-    @Test
-    public void testAnd_uninitialized() {
-        MutableLiveData<Boolean> empty = new MutableLiveData<>();
-        checkUninitializedBinary(
-                LiveDataFunctions::and,
-                pair(trueLiveData(), empty),
-                pair(falseLiveData(), empty),
-                pair(nullLiveData(), empty),
-                pair(empty, trueLiveData()),
-                pair(empty, falseLiveData()),
-                pair(empty, nullLiveData()));
-    }
-
-    @Test
-    public void testAnd_changeValue() {
-        MutableLiveData<Boolean> source = new MutableLiveData<>();
-        CaptureObserver<Boolean> observer = new CaptureObserver<>();
-
-        LiveDataFunctions.and(trueLiveData(), source).observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        source.setValue(true);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isTrue();
-    }
-
-    @Test
-    public void testOr_truthTable() {
-        testBinaryOperator(
-                LiveDataFunctions::or,
-                pair(pair(trueLiveData(), trueLiveData()), true),
-                pair(pair(trueLiveData(), falseLiveData()), true),
-                pair(pair(trueLiveData(), nullLiveData()), true),
-                pair(pair(falseLiveData(), trueLiveData()), true),
-                pair(pair(falseLiveData(), falseLiveData()), false),
-                pair(pair(falseLiveData(), nullLiveData()), null),
-                pair(pair(nullLiveData(), trueLiveData()), true),
-                pair(pair(nullLiveData(), falseLiveData()), null),
-                pair(pair(nullLiveData(), nullLiveData()), null));
-    }
-
-    @Test
-    public void testOr_uninitialized() {
-        LiveData<Boolean> empty = new MutableLiveData<>();
-        checkUninitializedBinary(
-                LiveDataFunctions::or,
-                pair(trueLiveData(), empty),
-                pair(falseLiveData(), empty),
-                pair(nullLiveData(), empty),
-                pair(empty, trueLiveData()),
-                pair(empty, falseLiveData()),
-                pair(empty, nullLiveData()));
-    }
-
-    @Test
-    public void testOr_changeValue() {
-        MutableLiveData<Boolean> source = new MutableLiveData<>();
-        CaptureObserver<Boolean> observer = new CaptureObserver<>();
-
-        LiveDataFunctions.or(trueLiveData(), source).observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        source.setValue(true);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isTrue();
-    }
-
-    @Test
-    public void testIff_truthTable() {
-        Object object = new Object();
-        testBinaryOperator(
-                (predicate, value) -> LiveDataFunctions.iff(predicate, Boolean::booleanValue,
-                        value),
-                pair(pair(trueLiveData(), dataOf(object)), object),
-                pair(pair(falseLiveData(), dataOf(object)), null),
-                pair(pair(nullLiveData(), dataOf(object)), null));
-    }
-
-    @Test
-    public void testIff_uninitialized() {
-        checkUninitializedBinary(
-                (predicate, value) -> LiveDataFunctions.iff(predicate, Boolean::booleanValue,
-                        value),
-                pair(new MutableLiveData<>(), dataOf(new Object())),
-                pair(falseLiveData(), new MutableLiveData<>()),
-                pair(trueLiveData(), new MutableLiveData<>()));
-    }
-
-    @Test
-    public void testIff_changePredicate() {
-        MutableLiveData<Boolean> predicate = new MutableLiveData<>();
-        MutableLiveData<Object> value = new MutableLiveData<>();
-        Object valueObject = new Object();
-        value.setValue(valueObject);
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        LiveDataFunctions.iff(predicate, Boolean::booleanValue, value)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        predicate.setValue(false);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-        observer.reset();
-
-        predicate.setValue(true);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(valueObject);
-        observer.reset();
-
-        predicate.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-    }
-
-    @Test
-    public void testIff_changeValue() {
-        LiveData<Boolean> predicate = trueLiveData();
-        MutableLiveData<Object> value = new MutableLiveData<>();
-        Object firstObject = new Object();
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        LiveDataFunctions.iff(predicate, Boolean::booleanValue, value)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        value.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-        observer.reset();
-
-        value.setValue(firstObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(firstObject);
-        observer.reset();
-
-        value.setValue(new Object());
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNotSameInstanceAs(firstObject);
-    }
-
-    @Test
-    public void testIff_changeValue_doesntNotifyForIrrelevantChanges() {
-        MutableLiveData<Object> irrelevantValue = new MutableLiveData<>();
-        irrelevantValue.setValue(new Object());
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        // irrelevantValue irrelevant because iff() always emits null when predicate is false.
-        LiveDataFunctions.iff(falseLiveData(), Boolean::booleanValue, irrelevantValue)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        observer.reset();
-
-        irrelevantValue.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isFalse();
-    }
-
-    @Test
-    public void testIfThenElse_liveDataParams_truthTable() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        LiveData<Object> trueObjectData = dataOf(trueObject);
-        LiveData<Object> falseObjectData = dataOf(falseObject);
-
-        testOperator(arg -> () ->
-                        ifThenElse(arg.mPredicate, Boolean::booleanValue, arg.mTrueData,
-                                arg.mFalseData),
-                pair(new IfThenElseDataParams<>(trueLiveData(), trueObjectData, falseObjectData),
-                        trueObject),
-                pair(new IfThenElseDataParams<>(falseLiveData(), trueObjectData, falseObjectData),
-                        falseObject),
-                pair(new IfThenElseDataParams<>(dataOf(null), trueObjectData, falseObjectData),
-                        null));
-    }
-
-    @Test
-    public void testIfThenElse_liveDataParams_uninitialized() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        LiveData<Object> trueObjectData = dataOf(trueObject);
-        LiveData<Object> falseObjectData = dataOf(falseObject);
-
-        checkUninitialized(
-                ifThenElse(
-                        new MutableLiveData<>(), Boolean::booleanValue, trueObjectData,
-                        falseObjectData));
-        checkUninitialized(
-                ifThenElse(
-                        trueLiveData(), Boolean::booleanValue, new MutableLiveData<>(),
-                        falseObjectData));
-        checkUninitialized(
-                ifThenElse(
-                        falseLiveData(), Boolean::booleanValue, trueObjectData,
-                        new MutableLiveData<>()));
-    }
-
-    @Test
-    public void testIfThenElse_liveDataParams_changePredicate() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        LiveData<Object> trueObjectData = dataOf(trueObject);
-        LiveData<Object> falseObjectData = dataOf(falseObject);
-
-        MutableLiveData<Boolean> predicate = new MutableLiveData<>();
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        ifThenElse(predicate, Boolean::booleanValue, trueObjectData,
-                falseObjectData)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        predicate.setValue(false);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(falseObject);
-        observer.reset();
-
-        predicate.setValue(true);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(trueObject);
-        observer.reset();
-
-        predicate.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-    }
-
-    @Test
-    public void testIfThenElse_liveDataParams_changeValue_value() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        MutableLiveData<Object> trueObjectData = new MutableLiveData<>();
-        LiveData<Object> falseObjectData = dataOf(falseObject);
-
-        LiveData<Boolean> predicate = trueLiveData();
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        ifThenElse(predicate, Boolean::booleanValue, trueObjectData,
-                falseObjectData)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        trueObjectData.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-        observer.reset();
-
-        trueObjectData.setValue(trueObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(trueObject);
-        observer.reset();
-
-        trueObjectData.setValue(new Object());
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNotSameInstanceAs(trueObject);
-    }
-
-    @Test
-    public void testIfThenElse_liveDataParams_changeValue_doesntNotifyForIrrelevantChanges() {
-        MutableLiveData<Object> irrelevantValue = new MutableLiveData<>();
-        irrelevantValue.setValue(new Object());
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        // irrelevantValue irrelevant because ifThenElse() is backed by other param when
-        // predicate is
-        // false.
-        ifThenElse(
-                falseLiveData(), Boolean::booleanValue, irrelevantValue, dataOf(new Object()))
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isTrue();
-        observer.reset();
-
-        irrelevantValue.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isFalse();
-    }
-
-    @Test
-    public void testIfThenElse_valueParams_truthTable() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        testOperator(arg -> () -> ifThenElse(arg.mPredicate, Boolean::booleanValue, arg.mTrueValue,
-                arg.mFalseValue),
-                pair(new IfThenElseValueParams<>(trueLiveData(), trueObject, falseObject),
-                        trueObject),
-                pair(new IfThenElseValueParams<>(falseLiveData(), trueObject, falseObject),
-                        falseObject),
-                pair(new IfThenElseValueParams<>(dataOf(null), trueObject, falseObject), null));
-    }
-
-    @Test
-    public void testIfThenElse_valueParams_uninitialized() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        checkUninitialized(
-                ifThenElse(new MutableLiveData<>(), Boolean::booleanValue, trueObject,
-                        falseObject));
-    }
-
-    @Test
-    public void testIfThenElse_valueParams_changePredicate() {
-        Object trueObject = new Object();
-        Object falseObject = new Object();
-
-        MutableLiveData<Boolean> predicate = new MutableLiveData<>();
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-
-        ifThenElse(predicate, Boolean::booleanValue, trueObject, falseObject)
-                .observe(mLifecycleOwner, observer);
-        assertThat(observer.hasBeenNotified()).isFalse();
-
-        predicate.setValue(false);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(falseObject);
-        observer.reset();
-
-        predicate.setValue(true);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(trueObject);
-        observer.reset();
-
-        predicate.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-    }
-
-    @Test
-    public void testCoalesceNull_liveDataParams_truthTable() {
-        TestObject sourceObject = new TestObject();
-        TestObject fallbackObject = new TestObject();
-
-        LiveData<TestObject> sourceData = dataOf(sourceObject);
-        LiveData<TestObject> fallbackData = dataOf(fallbackObject);
-        testBinaryOperator(LiveDataFunctions::coalesceNull,
-                pair(pair(sourceData, fallbackData), sourceObject),
-                pair(pair(sourceData, nullLiveData()), sourceObject),
-                // uninitialized fallback is fine.
-                pair(pair(sourceData, new MutableLiveData<>()), sourceObject),
-                pair(pair(nullLiveData(), fallbackData), fallbackObject),
-                pair(pair(nullLiveData(), nullLiveData()), null));
-    }
-
-    @Test
-    public void testCoalesceNull_liveDataParams_uninitialized() {
-        TestObject fallbackObject = new TestObject();
-        LiveData<TestObject> fallbackData = dataOf(fallbackObject);
-
-        checkUninitialized(coalesceNull(new MutableLiveData<TestObject>(), fallbackData));
-    }
-
-    @Test
-    public void testCoalesceNull_liveDataParams_changeSource() {
-        TestObject firstSourceObject = new TestObject();
-        TestObject secondSourceObject = new TestObject();
-        TestObject fallbackObject = new TestObject();
-
-        MutableLiveData<TestObject> sourceData = dataOf(null);
-        LiveData<TestObject> fallbackData = dataOf(fallbackObject);
-
-        CaptureObserver<TestObject> observer = new CaptureObserver<>();
-        LiveData<TestObject> data = coalesceNull(sourceData, fallbackData);
-
-        data.observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(fallbackObject);
-        observer.reset();
-
-        sourceData.setValue(firstSourceObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(firstSourceObject);
-        observer.reset();
-
-        sourceData.setValue(secondSourceObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(secondSourceObject);
-        observer.reset();
-
-        sourceData.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(fallbackObject);
-        observer.reset();
-    }
-
-    @Test
-    public void testCoalesceNull_liveDataParams_changeFallback() {
-        TestObject firstFallbackObject = new TestObject();
-        TestObject secondFallbackObject = new TestObject();
-
-        LiveData<TestObject> sourceData = nullLiveData();
-        MutableLiveData<TestObject> fallbackData = dataOf(null);
-
-        CaptureObserver<TestObject> observer = new CaptureObserver<>();
-        LiveData<TestObject> data = coalesceNull(sourceData, fallbackData);
-
-        data.observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isNull();
-        observer.reset();
-
-        fallbackData.setValue(firstFallbackObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(firstFallbackObject);
-        observer.reset();
-
-        fallbackData.setValue(secondFallbackObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(secondFallbackObject);
-        observer.reset();
-
-        fallbackData.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(null);
-        observer.reset();
-    }
-
-    @Test
-    public void testCoalesceNull_liveDataParams_changeFallback_doesntNotifyForIrrelevantChanges() {
-        TestObject sourceObject = new TestObject();
-        TestObject fallbackObject = new TestObject();
-
-        LiveData<TestObject> sourceData = dataOf(sourceObject);
-        // Irrelevant because sourceData is always non-null
-        MutableLiveData<TestObject> irrelevantData = dataOf(fallbackObject);
-
-        CaptureObserver<TestObject> observer = new CaptureObserver<>();
-        LiveData<TestObject> data = coalesceNull(sourceData, irrelevantData);
-        data.observe(mLifecycleOwner, observer);
-        observer.reset();
-
-        irrelevantData.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isFalse();
-    }
-
-    @Test
-    public void testCoalesceNull_valueParams_truthTable() {
-        Object sourceObject = new Object();
-        Object fallbackObject = new Object();
-
-        LiveData<Object> sourceData = dataOf(sourceObject);
-
-        testOperator(args -> () -> coalesceNull(Objects.requireNonNull(args.first), args.second),
-                pair(pair(sourceData, fallbackObject), sourceObject),
-                pair(pair(sourceData, null), sourceObject),
-                pair(pair(nullLiveData(), fallbackObject), fallbackObject),
-                pair(pair(nullLiveData(), null), null));
-
-    }
-
-    @Test
-    public void testCoalesceNull_valueParams_uninitialized() {
-        // Values contained in SoftReference don't actually matter. SoftReference is just used as
-        // an easily instantiable type. Object cannot be used because LiveData extends Object,
-        // and thus some method calls would become ambiguous due to overloads.
-        Object fallbackObject = new Object();
-
-        checkUninitialized(coalesceNull(new MutableLiveData<>(), fallbackObject));
-    }
-
-    @Test
-    public void testCoalesceNull_valueParams_changeSource() {
-        // Values contained in SoftReference don't actually matter. SoftReference is just used as
-        // an easily instantiable type. Object cannot be used because LiveData extends Object,
-        // and thus some method calls would become ambiguous.
-        Object firstSourceObject = new Object();
-        Object secondSourceObject = new Object();
-        Object fallbackObject = new Object();
-
-        MutableLiveData<Object> sourceData = dataOf(null);
-
-        CaptureObserver<Object> observer = new CaptureObserver<>();
-        LiveData<Object> data = coalesceNull(sourceData, fallbackObject);
-
-        data.observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(fallbackObject);
-        observer.reset();
-
-        sourceData.setValue(firstSourceObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(firstSourceObject);
-        observer.reset();
-
-        sourceData.setValue(secondSourceObject);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(secondSourceObject);
-        observer.reset();
-
-        sourceData.setValue(null);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isSameInstanceAs(fallbackObject);
-        observer.reset();
-    }
-
-    @Test
-    public void testSplit() {
-        Object first = new Object();
-        Object second = new Object();
-
-        MutableLiveData<Object> firstData = new MutableLiveData<>();
-        MutableLiveData<Object> secondData = new MutableLiveData<>();
-        firstData.setValue(first);
-        secondData.setValue(second);
-
-        Object[] observedValues = new Object[2];
-        boolean[] notified = new boolean[1];
-
-        LiveDataFunctions.pair(firstData, secondData)
-                .observe(
-                        mLifecycleOwner,
-                        split(
-                                (left, right) -> {
-                                    notified[0] = true;
-                                    observedValues[0] = left;
-                                    observedValues[1] = right;
-                                }));
-
-        assertThat(notified[0]).isTrue();
-        assertThat(observedValues[0]).isSameInstanceAs(first);
-        assertThat(observedValues[1]).isSameInstanceAs(second);
-    }
-
-    @Test
-    public void testSplit_null() {
-        Object[] observedValues = new Object[2];
-        boolean[] notified = new boolean[1];
-
-        dataOf((Pair<Object, Object>) null)
-                .observe(
-                        mLifecycleOwner,
-                        split(
-                                (left, right) -> {
-                                    notified[0] = true;
-                                    observedValues[0] = left;
-                                    observedValues[1] = right;
-                                }));
-
-        assertThat(notified[0]).isTrue();
-        assertThat(observedValues[0]).isNull();
-        assertThat(observedValues[1]).isNull();
-    }
-
-    @Test
-    public void testCombine() {
-        Object first = new Object();
-        Object second = new Object();
-
-        MutableLiveData<Object> firstData = new MutableLiveData<>();
-        MutableLiveData<Object> secondData = new MutableLiveData<>();
-        firstData.setValue(first);
-        secondData.setValue(second);
-
-        CaptureObserver<Pair<Object, Object>> observer = new CaptureObserver<>();
-        LiveDataFunctions.combine(firstData, secondData, Pair::new).observe(mLifecycleOwner,
-                observer);
-
-        Pair<Object, Object> observedValue = observer.getObservedValue();
-        assertThat(observedValue).isNotNull();
-        assertThat(observedValue.first).isSameInstanceAs(first);
-        assertThat(observedValue.second).isSameInstanceAs(second);
-
-        Object third = new Object();
-        firstData.setValue(third);
-
-        observedValue = observer.getObservedValue();
-        assertThat(observedValue).isNotNull();
-        assertThat(observedValue.first).isSameInstanceAs(third);
-        assertThat(observedValue.second).isSameInstanceAs(second);
-    }
-
-    private static class IfThenElseDataParams<T> {
-        final LiveData<Boolean> mPredicate;
-        final LiveData<T> mTrueData;
-        final LiveData<T> mFalseData;
-
-        private IfThenElseDataParams(
-                LiveData<Boolean> predicate, LiveData<T> trueData, LiveData<T> falseData) {
-            this.mPredicate = predicate;
-            this.mTrueData = trueData;
-            this.mFalseData = falseData;
-        }
-    }
-
-    private static class IfThenElseValueParams<T> {
-        final LiveData<Boolean> mPredicate;
-        final T mTrueValue;
-        final T mFalseValue;
-
-        private IfThenElseValueParams(LiveData<Boolean> predicate, T trueValue, T falseValue) {
-            this.mPredicate = predicate;
-            this.mTrueValue = trueValue;
-            this.mFalseValue = falseValue;
-        }
-    }
-
-    private <R> void testOperator(Supplier<LiveData<R>> op, R result) {
-        CaptureObserver<R> observer = new CaptureObserver<>();
-        LiveData<R> data = op.get();
-
-        data.observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isTrue();
-        assertThat(observer.getObservedValue()).isEqualTo(result);
-        assertThat(data.getValue()).isEqualTo(result);
-    }
-
-    @SafeVarargs // args are never written to
-    private final <P, R> void testOperator(
-            Function<P, Supplier<LiveData<R>>> ops, Pair<P, R>... args) {
-        for (Pair<P, R> arg : args) {
-            testOperator(ops.apply(arg.first), arg.second);
-        }
-    }
-
-    @SafeVarargs // args are never written to
-    private final <T, R> void testUnaryOperator(
-            Function<LiveData<T>, LiveData<R>> op, Pair<LiveData<T>, R>... args) {
-        testOperator(arg -> () -> op.apply(arg), args);
-    }
-
-    @SafeVarargs // args are never written to
-    private final <A, B, R> void testBinaryOperator(
-            BiFunction<LiveData<A>, LiveData<B>, LiveData<R>> op,
-            Pair<Pair<LiveData<A>, LiveData<B>>, R>... args) {
-        testOperator(arg -> () -> op.apply(arg.first, arg.second), args);
-    }
-
-    private <T, R> Pair<T, R> pair(T first, R second) {
-        return new Pair<>(first, second);
-    }
-
-    private <T> void checkUninitialized(LiveData<T> liveData) {
-        CaptureObserver<T> observer = new CaptureObserver<>();
-
-        liveData.observe(mLifecycleOwner, observer);
-
-        assertThat(observer.hasBeenNotified()).isFalse();
-        assertThat(liveData.getValue()).isNull();
-    }
-
-    @SafeVarargs // args are never written to
-    private final <A, B> void checkUninitializedBinary(
-            BiFunction<LiveData<A>, LiveData<B>, LiveData<?>> op,
-            Pair<LiveData<A>, LiveData<B>>... args) {
-        for (Pair<LiveData<A>, LiveData<B>> arg : args) {
-            checkUninitialized(op.apply(arg.first, arg.second));
-        }
-    }
-
-    /**
-     * Used as an easily instantiable type where Object cannot be used because LiveData extends
-     * Object, and thus some method calls would become ambiguous.
-     **/
-    private class TestObject {
-    }
-}
diff --git a/car-assist-lib/Android.bp b/car-assist-lib/Android.bp
index 77d8c26..0a5f548 100644
--- a/car-assist-lib/Android.bp
+++ b/car-assist-lib/Android.bp
@@ -29,6 +29,10 @@
 
     sdk_version: "current",
 
+    min_sdk_version: "28",
+
+    target_sdk_version: "31",
+
     static_libs: [
         // ensure unbundled
         "car-messaging-models",
diff --git a/car-assist-lib/build.gradle b/car-assist-lib/build.gradle
new file mode 100644
index 0000000..77a39dd
--- /dev/null
+++ b/car-assist-lib/build.gradle
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2021 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.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+        }
+    }
+}
+
+dependencies {
+    runtimeOnly 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'androidx.annotation:annotation:1.2.0'
+    implementation 'androidx.core:core:1.6.0'
+    implementation project(path: ':car-messenger-common:model')
+}
diff --git a/car-assist-lib/src/com/android/car/assist/payloadhandlers/NotificationPayloadHandler.java b/car-assist-lib/src/com/android/car/assist/payloadhandlers/NotificationPayloadHandler.java
index b834fd6..0251cb0 100644
--- a/car-assist-lib/src/com/android/car/assist/payloadhandlers/NotificationPayloadHandler.java
+++ b/car-assist-lib/src/com/android/car/assist/payloadhandlers/NotificationPayloadHandler.java
@@ -70,6 +70,21 @@
     }
 
     /**
+     * Retrieves all messages associated with the provided {@link Notification}
+     * These messages are provided through the notification's {@link MessagingStyle},
+     * using {@link MessagingStyle#addMessage(Message)}.
+     *
+     * @param notification the notification delivered to the voice interaction session
+     * @return all messages provided in the {@link MessagingStyle}
+     */
+    public List<Message> getMessages(Notification notification) {
+        MessagingStyle messagingStyle = NotificationCompat.MessagingStyle
+                .extractMessagingStyleFromNotification(notification);
+
+        return messagingStyle == null ? new ArrayList<>() : messagingStyle.getMessages();
+    }
+
+    /**
      * Retrieves all messages associated with the provided {@link StatusBarNotification} in the
      * args {@link Bundle}. These messages are provided through the notification's
      * {@link MessagingStyle}, using {@link MessagingStyle#addMessage(Message)}.
@@ -79,11 +94,27 @@
      */
     public List<Message> getMessages(Bundle args) {
         Notification notification = getNotification(args);
+        return getMessages(notification);
+    }
 
-        MessagingStyle messagingStyle = NotificationCompat.MessagingStyle
-                .extractMessagingStyleFromNotification(notification);
+    /**
+     * Retrieves the corresponding {@link Action} from the notification's callback actions.
+     *
+     * @param notification the notification delivered to the voice interaction session
+     * @param semanticAction the {@link Action.SemanticAction} on which to select
+     * @return the first action for which {@link Action#getSemanticAction()} returns semanticAction,
+     * or null if no such action exists
+     */
+    @Nullable
+    public Action getAction(Notification notification, int semanticAction) {
+        for (Action action : notification.actions) {
+            if (action.getSemanticAction() == semanticAction) {
+                return action;
+            }
+        }
 
-        return messagingStyle == null ? new ArrayList<>() : messagingStyle.getMessages();
+        Log.w(TAG, String.format("Semantic action not found: %d", semanticAction));
+        return null;
     }
 
     /**
@@ -97,20 +128,7 @@
     @Nullable
     public Action getAction(Bundle args, int semanticAction) {
         Notification notification = getNotification(args);
-
-        if (notification == null) {
-            Log.w(TAG, "getAction args bundle did not contain a notification");
-            return null;
-        }
-
-        for (Action action : notification.actions) {
-            if (action.getSemanticAction() == semanticAction) {
-                return action;
-            }
-        }
-
-        Log.w(TAG, String.format("Semantic action not found: %d", semanticAction));
-        return null;
+        return getAction(notification, semanticAction);
     }
 
     /**
diff --git a/car-media-common/Android.bp b/car-media-common/Android.bp
index 9ec491f..cbd6812 100644
--- a/car-media-common/Android.bp
+++ b/car-media-common/Android.bp
@@ -36,10 +36,12 @@
         "androidx.mediarouter_mediarouter",
         "androidx-constraintlayout_constraintlayout",
         "car-apps-common",
-        "car-arch-common",
         "androidx-constraintlayout_constraintlayout-solver",
     ],
 
     libs: ["android.car-system-stubs"],
-    sdk_version: "system_current",
+
+    min_sdk_version: "30", // Media requires apis that became public in R.
+    target_sdk_version: "31",
+    sdk_version: "current",
 }
diff --git a/car-media-common/OWNERS b/car-media-common/OWNERS
index ff47d11..dff7054 100644
--- a/car-media-common/OWNERS
+++ b/car-media-common/OWNERS
@@ -1,4 +1,3 @@
 # People who can approve changes for submission.
-keyboardr@google.com
 robertoalexis@google.com
 arnaudberry@google.com
diff --git a/car-media-common/build.gradle b/car-media-common/build.gradle
new file mode 100644
index 0000000..1464da5
--- /dev/null
+++ b/car-media-common/build.gradle
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Library-level build file
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 30 // Media requires apis that became public in R.
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+}
+
+dependencies {
+    implementation "androidx.cardview:cardview:1.0.0"
+    implementation "androidx.interpolator:interpolator:1.0.0"
+
+    def lifecycle_version = "2.2.0"
+    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+    // Not available in 2.3+
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
+    implementation 'androidx.recyclerview:recyclerview:1.2.1'
+
+    implementation "androidx.media:media:1.4.1"
+    implementation files(gradle.ext.lib_car_system_stubs)
+
+    implementation project(":car-apps-common")
+}
diff --git a/car-media-common/src/com/android/car/media/common/MediaButtonController.java b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
index 1db7a31..5a7f7f6 100644
--- a/car-media-common/src/com/android/car/media/common/MediaButtonController.java
+++ b/car-media-common/src/com/android/car/media/common/MediaButtonController.java
@@ -230,6 +230,8 @@
     }
 
     private void updateCustomActions(@Nullable PlaybackViewModel.PlaybackStateWrapper state) {
+        int focusedViewIndex = mControlBar.getFocusedViewIndex();
+
         List<ImageButton> imageButtons = new ArrayList<>();
         if (state != null) {
             imageButtons.addAll(state.getCustomActions()
@@ -245,6 +247,8 @@
             mControlBar.setView(imageButtons.remove(0), CarControlBar.SLOT_RIGHT);
         }
         mControlBar.setViews(imageButtons.toArray(new ImageButton[0]));
+
+        mControlBar.setFocusAtViewIndex(focusedViewIndex);
     }
 
     private ImageButton getOrCreateIconButton(CustomPlaybackAction action) {
diff --git a/car-media-common/src/com/android/car/media/common/MetadataController.java b/car-media-common/src/com/android/car/media/common/MetadataController.java
index e43753f..8102049 100644
--- a/car-media-common/src/com/android/car/media/common/MetadataController.java
+++ b/car-media-common/src/com/android/car/media/common/MetadataController.java
@@ -16,8 +16,8 @@
 
 package com.android.car.media.common;
 
-import static com.android.car.arch.common.LiveDataFunctions.combine;
-import static com.android.car.arch.common.LiveDataFunctions.pair;
+import static com.android.car.apps.common.util.LiveDataFunctions.combine;
+import static com.android.car.apps.common.util.LiveDataFunctions.pair;
 
 import android.content.Context;
 import android.text.TextUtils;
diff --git a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
index d9ff4b1..a7e3b08 100644
--- a/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
+++ b/car-media-common/src/com/android/car/media/common/PlaybackFragment.java
@@ -18,7 +18,7 @@
 
 import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK;
 
-import static com.android.car.arch.common.LiveDataFunctions.mapNonNull;
+import static com.android.car.apps.common.util.LiveDataFunctions.mapNonNull;
 
 import android.app.Application;
 import android.app.PendingIntent;
@@ -49,8 +49,8 @@
 import com.android.car.apps.common.imaging.ImageBinder;
 import com.android.car.apps.common.imaging.ImageBinder.PlaceholderType;
 import com.android.car.apps.common.util.CarPackageManagerUtils;
+import com.android.car.apps.common.util.FutureData;
 import com.android.car.apps.common.util.ViewUtils;
-import com.android.car.arch.common.FutureData;
 import com.android.car.media.common.browse.MediaBrowserViewModelImpl;
 import com.android.car.media.common.browse.MediaItemsRepository;
 import com.android.car.media.common.playback.PlaybackViewModel;
diff --git a/car-media-common/src/com/android/car/media/common/browse/MediaItemsRepository.java b/car-media-common/src/com/android/car/media/common/browse/MediaItemsRepository.java
index e01cb96..7f099a5 100644
--- a/car-media-common/src/com/android/car/media/common/browse/MediaItemsRepository.java
+++ b/car-media-common/src/com/android/car/media/common/browse/MediaItemsRepository.java
@@ -16,7 +16,7 @@
 
 package com.android.car.media.common.browse;
 
-import static com.android.car.arch.common.LiveDataFunctions.dataOf;
+import static com.android.car.apps.common.util.LiveDataFunctions.dataOf;
 
 import static java.util.stream.Collectors.toList;
 
@@ -34,7 +34,7 @@
 import androidx.lifecycle.LiveData;
 import androidx.lifecycle.MutableLiveData;
 
-import com.android.car.arch.common.FutureData;
+import com.android.car.apps.common.util.FutureData;
 import com.android.car.media.common.MediaItemMetadata;
 import com.android.car.media.common.source.MediaBrowserConnector.BrowsingState;
 import com.android.car.media.common.source.MediaSource;
diff --git a/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java b/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
index 713caa1..5a183b3 100644
--- a/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
+++ b/car-media-common/src/com/android/car/media/common/playback/PlaybackViewModel.java
@@ -18,7 +18,7 @@
 
 import static androidx.lifecycle.Transformations.switchMap;
 
-import static com.android.car.arch.common.LiveDataFunctions.dataOf;
+import static com.android.car.apps.common.util.LiveDataFunctions.dataOf;
 import static com.android.car.media.common.playback.PlaybackStateAnnotations.Actions;
 
 import android.app.Application;
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java b/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
index 8c93426..0b297fe 100644
--- a/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
+++ b/car-media-common/src/com/android/car/media/common/source/MediaSourceViewModel.java
@@ -16,7 +16,7 @@
 
 package com.android.car.media.common.source;
 
-import static com.android.car.arch.common.LiveDataFunctions.dataOf;
+import static com.android.car.apps.common.util.LiveDataFunctions.dataOf;
 
 import android.app.Application;
 import android.car.Car;
@@ -111,6 +111,7 @@
         });
     }
 
+    private final int mMode;
     private final InputFactory mInputFactory;
     private final MediaBrowserConnector mBrowserConnector;
     private final MediaBrowserConnector.Callback mBrowserCallback = mBrowsingState::setValue;
@@ -120,6 +121,7 @@
             @NonNull InputFactory inputFactory) {
         super(application);
 
+        mMode = mode;
         mInputFactory = inputFactory;
         mCar = inputFactory.getCarApi();
 
@@ -132,7 +134,11 @@
         try {
             mCarMediaManager = mInputFactory.getCarMediaManager(mCar);
             mCarMediaManager.addMediaSourceListener(mMediaSourceListener, mode);
-            updateModelState(mInputFactory.getMediaSource(mCarMediaManager.getMediaSource(mode)));
+            MediaSource src = mInputFactory.getMediaSource(mCarMediaManager.getMediaSource(mode));
+            if (Log.isLoggable(TAG, Log.INFO)) {
+                Log.i(TAG, "Initializing with " + src);
+            }
+            updateModelState(src);
         } catch (CarNotConnectedException e) {
             Log.e(TAG, "Car not connected", e);
         }
@@ -160,6 +166,12 @@
      * Updates the primary media source.
      */
     public void setPrimaryMediaSource(@NonNull MediaSource mediaSource, int mode) {
+        if (mMode == mode) {
+            // Update the live data with the new value right away.
+            updateModelState(mediaSource);
+        } else if (Log.isLoggable(TAG, Log.WARN)) {
+            Log.w(TAG, "Inconsistent media source mode " + mode + " mMode: " + mMode);
+        }
         mCarMediaManager.setMediaSource(mediaSource.getBrowseServiceComponentName(), mode);
     }
 
diff --git a/car-media-common/src/com/android/car/media/common/source/MediaTrampolineHelper.java b/car-media-common/src/com/android/car/media/common/source/MediaTrampolineHelper.java
new file mode 100644
index 0000000..85ddc38
--- /dev/null
+++ b/car-media-common/src/com/android/car/media/common/source/MediaTrampolineHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.media.common.source;
+
+
+import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE;
+
+import android.app.Application;
+import android.car.media.CarMediaIntents;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+import java.util.Objects;
+
+
+/**
+ * This class helps different Media "Hosts" (CarMediaApp, CarRadioApp...) pretend they are a single
+ * app, by automatically launching the intent needed to see a newly activated media service in its
+ * proper host. For example when the user is displaying the Radio and activates by voice the local
+ * media player, that service becomes visible in the CarMediaApp. <p/>
+ *
+ * An activity should only use a single instance of this class, and should call
+ * {@link #setLaunchedMediaSource} when it is resumed with a new intent.
+ */
+public class MediaTrampolineHelper implements LifecycleObserver {
+
+    private static final String TAG = "MediaTrampolineHlp";
+    private final FragmentActivity mActivity;
+    private final MediaSourceViewModel mMediaSourceVM;
+
+
+    private MediaSource mLaunchedSource;
+    private boolean mIsResumed;
+
+    public MediaTrampolineHelper(@NonNull FragmentActivity activity) {
+        mActivity = activity;
+        Application app = mActivity.getApplication();
+        mMediaSourceVM = MediaSourceViewModel.get(app, MEDIA_SOURCE_MODE_BROWSE);
+        mActivity.getLifecycle().addObserver(this);
+
+        mMediaSourceVM.getPrimaryMediaSource().observe(mActivity, source -> {
+            if (mIsResumed) {
+                Log.i(TAG, "New media source while resumed: " + source);
+                maybeTrampoline(source);
+            }
+        });
+    }
+
+    /**
+     * Sets the media source that should be 1) recorded as primary, and 2) used as baseline when
+     * the helper detects a new media source and decides to trampoline to it.
+     * Should be called when the activity is resumed with a new intent (and the intent should be
+     * flagged as consumed).
+     */
+    public void setLaunchedMediaSource(@Nullable ComponentName launchedSourceComp) {
+        if (launchedSourceComp != null) {
+            mLaunchedSource = MediaSource.create(mActivity, launchedSourceComp);
+        }
+
+        if (mLaunchedSource != null) {
+            // Since the activity is being explicitly launched, make sure its source is primary.
+            mMediaSourceVM.setPrimaryMediaSource(mLaunchedSource, MEDIA_SOURCE_MODE_BROWSE);
+        } else {
+            // Might happen if there's no media source at all on the system ?!
+            Log.e(TAG, "Null mLaunchedSource!!!");
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            MediaSource currentSource = mMediaSourceVM.getPrimaryMediaSource().getValue();
+            Log.d(TAG, "Launched: " + mLaunchedSource + " current: " + currentSource);
+        }
+    }
+
+    /***/
+    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    public void onResume() {
+        mIsResumed = true;
+        maybeTrampoline(mMediaSourceVM.getPrimaryMediaSource().getValue());
+    }
+
+    /***/
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    public void onPause() {
+        mIsResumed = false;
+    }
+
+    private void maybeTrampoline(MediaSource latestSource) {
+        if (!Objects.equals(mLaunchedSource, latestSource)) {
+            // Always go through the trampoline activity to keep the dispatching logic there.
+
+            if (Log.isLoggable(TAG, Log.INFO)) {
+                Log.i(TAG, "Launching: " + latestSource + " old: " + mLaunchedSource);
+            }
+
+            Intent intent = new Intent(CarMediaIntents.ACTION_MEDIA_TEMPLATE);
+            if (latestSource != null) {
+                ComponentName component = latestSource.getBrowseServiceComponentName();
+                intent.putExtra(CarMediaIntents.EXTRA_MEDIA_COMPONENT, component.flattenToString());
+            } else if (Log.isLoggable(TAG, Log.WARN)) {
+                Log.w(TAG, "latestSource is null in onResume");
+            }
+            mActivity.startActivity(intent);
+        }
+    }
+}
diff --git a/car-media-common/tests/robotests/Android.bp b/car-media-common/tests/robotests/Android.bp
index c20ef82..6490412 100644
--- a/car-media-common/tests/robotests/Android.bp
+++ b/car-media-common/tests/robotests/Android.bp
@@ -15,7 +15,7 @@
     privileged: true,
 
     static_libs: [
-        "car-arch-common",
+        "car-apps-common",
         "car-media-common",
     ],
 }
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
index 33979e2..64f87b0 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/PlaybackViewModelTest.java
@@ -16,7 +16,7 @@
 
 package com.android.car.media.common.playback;
 
-import static com.android.car.arch.common.LiveDataFunctions.dataOf;
+import static com.android.car.apps.common.util.LiveDataFunctions.dataOf;
 import static com.android.car.media.common.MediaTestUtils.newFakeMediaSource;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -36,9 +36,9 @@
 import androidx.annotation.NonNull;
 import androidx.lifecycle.MutableLiveData;
 
-import com.android.car.arch.common.testing.CaptureObserver;
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.CaptureObserver;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
 import com.android.car.media.common.MediaItemMetadata;
 import com.android.car.media.common.source.MediaBrowserConnector.BrowsingState;
 import com.android.car.media.common.source.MediaBrowserConnector.ConnectionStatus;
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/ProgressLiveDataTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/ProgressLiveDataTest.java
index 991e4f2..ed396c3 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/playback/ProgressLiveDataTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/playback/ProgressLiveDataTest.java
@@ -24,9 +24,9 @@
 
 import androidx.lifecycle.Lifecycle;
 
-import com.android.car.arch.common.testing.CaptureObserver;
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.CaptureObserver;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaBrowserConnectorTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaBrowserConnectorTest.java
index fdf5a56..5c9bb60 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaBrowserConnectorTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaBrowserConnectorTest.java
@@ -30,8 +30,8 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
 import com.android.car.media.common.source.MediaBrowserConnector.BrowsingState;
 import com.android.car.media.common.source.MediaBrowserConnector.ConnectionStatus;
 
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourceViewModelTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourceViewModelTest.java
index 638f8dc..196553f 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourceViewModelTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourceViewModelTest.java
@@ -35,9 +35,9 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.car.arch.common.testing.CaptureObserver;
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.CaptureObserver;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
 import com.android.car.media.common.source.MediaBrowserConnector.BrowsingState;
 import com.android.car.media.common.source.MediaBrowserConnector.ConnectionStatus;
 
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
index 79dc196..afd16b4 100644
--- a/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/source/MediaSourcesProviderTest.java
@@ -32,8 +32,8 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.car.arch.common.testing.InstantTaskExecutorRule;
-import com.android.car.arch.common.testing.TestLifecycleOwner;
+import com.android.car.apps.common.testutils.InstantTaskExecutorRule;
+import com.android.car.apps.common.testutils.TestLifecycleOwner;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/car-messenger-common/Android.bp b/car-messenger-common/Android.bp
index 96fbf15..aaaee78 100644
--- a/car-messenger-common/Android.bp
+++ b/car-messenger-common/Android.bp
@@ -21,6 +21,8 @@
 android_library {
     name: "car-messenger-common",
 
+    manifest: "AndroidManifest-unbundled.xml",
+
     srcs: ["src/**/*.java"],
 
     optimize: {
@@ -28,23 +30,27 @@
     },
 
     sdk_version: "system_current",
+
     min_sdk_version: "28",
 
+    target_sdk_version: "31",
+
     libs: ["android.car-system-stubs",],
 
-    resource_dirs: ["res"],
-
     static_libs: [
-        "car-apps-common",
-        "car-messenger-protos",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.annotation_annotation",
+        "car-assist-lib",
         "car-telephony-common",
-        "libphonenumber",
     ],
 }
+
 // Car Messaging Models, Unbundled
 android_library {
     name: "car-messaging-models",
 
+    manifest: "AndroidManifest-unbundled.xml",
+
     srcs: [
       "src/com/android/car/messenger/common/Conversation.java",
     ],
@@ -55,6 +61,10 @@
 
     sdk_version: "current",
 
+    min_sdk_version: "28",
+
+    target_sdk_version: "31",
+
     static_libs: [
         "androidx.legacy_legacy-support-v4",
         "androidx.annotation_annotation",
diff --git a/car-messenger-common/AndroidManifest.xml b/car-messenger-common/AndroidManifest-unbundled.xml
similarity index 72%
copy from car-messenger-common/AndroidManifest.xml
copy to car-messenger-common/AndroidManifest-unbundled.xml
index 148737d..26f6ae1 100644
--- a/car-messenger-common/AndroidManifest.xml
+++ b/car-messenger-common/AndroidManifest-unbundled.xml
@@ -1,6 +1,6 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" ?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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.
@@ -16,6 +16,4 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.messenger.common">
-    <uses-permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
-</manifest>
+          package="com.android.car.messenger.common"/>
\ No newline at end of file
diff --git a/car-messenger-common/build.gradle b/car-messenger-common/build.gradle
new file mode 100644
index 0000000..cfc2b85
--- /dev/null
+++ b/car-messenger-common/build.gradle
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2021 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.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest-unbundled.xml'
+            java.srcDirs = ['src']
+            java.excludes = ['com/android/car/messenger/common/Conversation.java']
+        }
+    }
+
+    buildFeatures {
+        buildConfig = false
+    }
+}
+
+dependencies {
+    runtimeOnly 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'androidx.annotation:annotation:1.2.0'
+    implementation 'androidx.core:core:1.6.0'
+    implementation files(gradle.ext.lib_car_system_stubs)
+    implementation project(path: ':car-assist-lib')
+    implementation project(path: ':car-telephony-common')
+}
diff --git a/car-messenger-common/model/build.gradle b/car-messenger-common/model/build.gradle
new file mode 100644
index 0000000..bbaeace
--- /dev/null
+++ b/car-messenger-common/model/build.gradle
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2021 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.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile '../AndroidManifest-unbundled.xml'
+            java.srcDirs = ['../src']
+            java.includes = ['com/android/car/messenger/common/Conversation.java']
+        }
+    }
+
+    buildFeatures {
+        buildConfig = false
+    }
+}
+
+dependencies {
+    runtimeOnly 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'androidx.annotation:annotation:1.2.0'
+    implementation 'androidx.core:core:1.6.0'
+}
diff --git a/car-messenger-common/proto/Android.bp b/car-messenger-common/proto/Android.bp
deleted file mode 100644
index dde012f..0000000
--- a/car-messenger-common/proto/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library_static {
-    name: "car-messenger-protos",
-    host_supported: true,
-    proto: {
-        type: "lite",
-    },
-    srcs: ["*.proto"],
-    jarjar_rules: "jarjar-rules.txt",
-    sdk_version: "28",
-}
diff --git a/car-messenger-common/proto/jarjar-rules.txt b/car-messenger-common/proto/jarjar-rules.txt
deleted file mode 100644
index d27aecb..0000000
--- a/car-messenger-common/proto/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule com.google.protobuf.** com.android.car.protobuf.@1
diff --git a/car-messenger-common/proto/notification_msg.proto b/car-messenger-common/proto/notification_msg.proto
deleted file mode 100644
index d3b87ab..0000000
--- a/car-messenger-common/proto/notification_msg.proto
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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.
- */
-
-syntax = "proto3";
-
-package com.android.car.messenger.proto;
-
-option java_package = "com.android.car.messenger.NotificationMsgProto";
-
-// Message to be sent from the phone SDK to the IHU SDK.
-message PhoneToCarMessage {
-  // The unique key of the message notification, same in phone and car.
-  // This will be the StatusBarNotification id of the original message
-  // notification posted on the phone.
-  string notification_key = 1;
-
-  // The different types of messages to be sent from the phone SDK.
-  oneof message_data {
-    // Metadata of a new conversation (new in the history of the current
-    // connection between phone and IHU SDKs).
-    ConversationNotification conversation = 2;
-    // Metadata of a new conversation received in an existing conversation.
-    MessagingStyleMessage message = 3;
-    // Fulfillment update of an action that was requested previously by
-    // the IHU SDK.
-    ActionStatusUpdate status_update = 4;
-    // Metadata of a new sender avatar icon.
-    AvatarIconSync avatar_icon_sync = 5;
-    // Request to remove all data related to a messaging application.
-    ClearAppDataRequest clear_app_data_request = 6;
-    // Informs SDK whether this feature has been enabled/disabled.
-    FeatureEnabledStateChange feature_enabled_state_change = 7;
-    // Details about the connected phone.
-    PhoneMetadata phone_metadata = 8;
-  }
-
-  // A byte array containing an undefined message. This field may contain
-  // supplemental information for a message_data, or contain all of the
-  // data for the PhoneToCarMessage.
-  bytes metadata = 9;
-}
-
-// Message to be sent from the IHU SDK to the phone SDK.
-message CarToPhoneMessage {
-  // The unique key of the message notification, same in phone and car.
-  // This will be the StatusBarNotification id of the original message
-  // notification posted on the phone.
-  string notification_key = 1;
-
-  // An action request to be fulfilled on the Phone side.
-  Action action_request = 2;
-
-  // A byte array containing an undefined message. This field may contain
-  // supplemental information for a message_data, or contain all of the
-  // data for the CarToPhoneMessage.
-  bytes metadata = 3;
-}
-
-// Message to be sent from the Phone SDK to the IHU SDK after an Action
-// has been completed. The request_id in this update will correspond to
-// the request_id of the original Action message.
-message ActionStatusUpdate {
-  // The different result types after completing an action.
-  enum Status {
-    UNKNOWN = 0;
-    SUCCESSFUL = 1;
-    ERROR = 2;
-  }
-
-  // Unique ID of the action.
-  string request_id = 1;
-
-  // The status of completing the action.
-  Status status = 2;
-
-  // Optional error message / explanation if the status resulted in an error.
-  string error_explanation = 3;
-}
-
-// A message notification originating from the user's phone.
-message ConversationNotification {
-
-  // Display name of the application that posted this notification.
-  string messaging_app_display_name = 1;
-
-  // Package name of the application that posted this notification.
-  string messaging_app_package_name = 2;
-
-  // MessagingStyle metadata of this conversation.
-  MessagingStyle messaging_style = 3;
-
-  // The time, in milliseconds, this message notification was last updated.
-  int64 time_ms = 4;
-
-  // Small app icon of the application that posted this notification.
-  bytes app_icon = 5;
-}
-
-// MessagingStyle metadata that matches MessagingStyle formatting.
-message MessagingStyle {
-  // List of messages and their metadata.
-  repeated MessagingStyleMessage messaging_style_msg = 1;
-
-  // The Conversation title of this conversation.
-  string convo_title = 2;
-
-  // String of the user, needed for MessagingStyle.
-  string user_display_name = 3;
-
-  // True if this is a group conversation.
-  bool is_group_convo = 4;
-}
-
-// Message metadata that matches MessagingStyle formatting.
-message MessagingStyleMessage {
-  // Contents of the message.
-  string text_message = 1;
-
-  // Timestamp of when the message notification was originally posted on the
-  // phone.
-  int64 timestamp = 2;
-
-  // Details of the sender who sent the message.
-  Person sender = 3;
-
-  // If the message is read on the phone.
-  bool is_read = 4;
-}
-
-// Sends over an avatar icon. This should be sent once per unique sender
-// (per unique app) during a phone to car connection.
-message AvatarIconSync {
-  // Metadata of the person.
-  Person person = 1;
-
-  // Display name of the application that posted this notification.
-  string messaging_app_display_name = 2;
-
-  // Package name of the application that posted this notification.
-  string messaging_app_package_name = 3;
-}
-
-// Request to clear all internal data and remove notifications for
-// a specific messaging application.
-message ClearAppDataRequest {
-  // Specifies which messaging app's data to remove.
-  string messaging_app_package_name = 1;
-}
-
-// Message to inform whether user has disabled/enabled this feature.
-message FeatureEnabledStateChange {
-  // Enabled state of the feature.
-  bool enabled = 1;
-}
-
-// Details of the phone that is connected to the IHU.
-message PhoneMetadata {
-  // MAC address of the phone.
-  string bluetooth_device_address = 1;
-}
-
-// Metadata about a sender.
-message Person {
-  // Sender's name.
-  string name = 1;
-
-  // Sender's avatar icon.
-  bytes avatar = 2;
-
-  // Sender's low-resolution thumbnail
-  bytes thumbnail = 3;
-}
-
-// Action on a notification, initiated by the user on the IHU.
-message Action {
-  // Different types of actions user can do on the IHU notification.
-  enum ActionName {
-    UNKNOWN_ACTION_NAME = 0;
-    MARK_AS_READ = 1;
-    REPLY = 2;
-    DISMISS = 3;
-  }
-
-  // Same as the PhoneToCar and CarToPhone messages's notification_key.
-  // As mentioned above, this notification id should be the same on the
-  // phone and the car. This will be the StatusBarNotification id of the
-  // original message notification posted on the phone.
-  string notification_key = 1;
-
-  //Optional, used to capture data like the reply string.
-  repeated MapEntry map_entry = 2;
-
-  // Name of the action.
-  ActionName action_name = 3;
-
-  // Unique id of this action.
-  string request_id = 4;
-}
-
-// Backwards compatible way of supporting a map.
-message MapEntry {
-  // Key for the map.
-  string key = 1;
-
-  // Value that is mapped to this key.
-  string value = 2;
-}
diff --git a/car-messenger-common/res/drawable/ic_message.xml b/car-messenger-common/res/drawable/ic_message.xml
deleted file mode 100644
index fd6026f..0000000
--- a/car-messenger-common/res/drawable/ic_message.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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" xmlns:aapt="http://schemas.android.com/aapt"
-    android:viewportWidth="48"
-    android:viewportHeight="48"
-    android:width="48dp"
-    android:height="48dp">
-  <path
-      android:pathData="M40 4H8C5.79 4 4.02 5.79 4.02 8L4 44l8 -8h28c2.21 0 4 -1.79 4 -4V8c0 -2.21 -1.79 -4 -4 -4zM12 18h24v4H12v-4zm16 10H12v-4h16v4zm8 -12H12v-4h24v4z"
-      android:fillColor="#FFFFFF" />
-</vector>
diff --git a/car-messenger-common/res/values-af/strings.xml b/car-messenger-common/res/values-af/strings.xml
deleted file mode 100644
index 7ffff1f..0000000
--- a/car-messenger-common/res/values-af/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d nuwe boodskappe</item>
-      <item quantity="one">Nuwe boodskap</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Speel"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Merk as gelees"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Herhaal"</string>
-    <string name="action_reply" msgid="564106590567600685">"Antwoord"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Maak toe"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Kan nie antwoord stuur nie Probeer weer."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Kan nie antwoord stuur nie Toestel is nie gekoppel nie."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s sê"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Kan nie boodskap hardop lees nie."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Antwoord is gestuur na %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Naam is nie beskikbaar nie"</string>
-</resources>
diff --git a/car-messenger-common/res/values-am/strings.xml b/car-messenger-common/res/values-am/strings.xml
deleted file mode 100644
index 8d11d19..0000000
--- a/car-messenger-common/res/values-am/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d አዲስ መልዕክቶች</item>
-      <item quantity="other">%d አዲስ መልዕክቶች</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"አጫውት"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"እንደተነበበ ምልክት አድርግ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ድገም"</string>
-    <string name="action_reply" msgid="564106590567600685">"ምላሽ ስጥ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"አስቁም"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ዝጋ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ምላሽ መላክ አልተቻለም። እባክዎ እንደገና ይሞክሩ።"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ምላሽ መላክ አልተቻለም። መሣሪያ አልተገናኘም።"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s እንዲህ ይላሉ፦"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"መልዕክቱን ማንበብ አልተቻለም።"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"«%s»"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"ምላሽ ለ%s ተልኳል"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ስም አይገኝም"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ar/strings.xml b/car-messenger-common/res/values-ar/strings.xml
deleted file mode 100644
index c2b2eb2..0000000
--- a/car-messenger-common/res/values-ar/strings.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="zero">%d رسالة جديدة.</item>
-      <item quantity="two">رسالتان جديدتان (%d)</item>
-      <item quantity="few">%d رسائل جديدة</item>
-      <item quantity="many">%d رسالة جديدة</item>
-      <item quantity="other">%d رسالة جديدة</item>
-      <item quantity="one">رسالة واحدة جديدة</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"تشغيل"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"وضع علامة \"مقروءة\""</string>
-    <string name="action_repeat" msgid="8184323082093728957">"تكرار"</string>
-    <string name="action_reply" msgid="564106590567600685">"رد"</string>
-    <string name="action_stop" msgid="6950369080845695405">"إيقاف"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"إغلاق"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"تعذّر إرسال رد. يُرجى إعادة المحاولة."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"تعذّر إرسال رد. الجهاز غير متصل."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"رسالة %s نصها"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"لا يمكن قراءة الرسالة بصوت عالٍ."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"تم إرسال رد إلى %s."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"الاسم غير متاح."</string>
-</resources>
diff --git a/car-messenger-common/res/values-as/strings.xml b/car-messenger-common/res/values-as/strings.xml
deleted file mode 100644
index bbf92d1..0000000
--- a/car-messenger-common/res/values-as/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%dটা নতুন বাৰ্তা</item>
-      <item quantity="other">%dটা নতুন বাৰ্তা</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"প্লে’ কৰক"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"পঢ়া হৈছে বুলি চিহ্নিত কৰক"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"পুনৰাই কৰক"</string>
-    <string name="action_reply" msgid="564106590567600685">"প্ৰত্যুত্তৰ দিয়ক"</string>
-    <string name="action_stop" msgid="6950369080845695405">"বন্ধ কৰক"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"বন্ধ কৰক"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"প্ৰত্যুত্তৰ পঠিয়াব নোৱাৰি। অনুগ্ৰহ কৰি পুনৰ চেষ্টা কৰক।"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"প্ৰত্যুত্তৰ পঠিয়াব নোৱাৰি। ডিভাইচটো সংযুক্ত হৈ নাই।"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%sএ কৈছে"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"বাৰ্তাটো পঢ়িব নোৱাৰি।"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%sলৈ প্রত্যুত্তৰ পঠিওৱা হ’ল"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"নাম উপলব্ধ নহয়"</string>
-</resources>
diff --git a/car-messenger-common/res/values-az/strings.xml b/car-messenger-common/res/values-az/strings.xml
deleted file mode 100644
index b93f53c..0000000
--- a/car-messenger-common/res/values-az/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d yeni mesaj</item>
-      <item quantity="one">Yeni mesaj</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Oxudun"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Oxunmuş kimi qeyd edin"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Təkrarlayın"</string>
-    <string name="action_reply" msgid="564106590567600685">"Cavablayın"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Dayandırın"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Bağlayın"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Cavab göndərmək alınmadı. Yenidən cəhd edin."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Cavab göndərmək alınmadı. Cihaz qoşulmayıb."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s söyləyir:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Mesajı oxumaq mümkün deyil."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Cavab bu ünvana göndərildi: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ad əlçatan deyil"</string>
-</resources>
diff --git a/car-messenger-common/res/values-b+sr+Latn/strings.xml b/car-messenger-common/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index 8421afb..0000000
--- a/car-messenger-common/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nova poruka</item>
-      <item quantity="few">%d nove poruke</item>
-      <item quantity="other">%d novih poruka</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Pusti"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označi kao pročitano"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ponovi"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odgovori"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zaustavi"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zatvori"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Slanje odgovora nije uspelo. Probajte ponovo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Slanje odgovora nije uspelo. Uređaj nije povezan."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s kaže"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Čitanje poruke naglas nije uspelo."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odgovor je poslat kontaktu %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ime nije dostupno"</string>
-</resources>
diff --git a/car-messenger-common/res/values-be/strings.xml b/car-messenger-common/res/values-be/strings.xml
deleted file mode 100644
index fa6e154..0000000
--- a/car-messenger-common/res/values-be/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d новае паведамленне</item>
-      <item quantity="few">%d новыя паведамленні</item>
-      <item quantity="many">%d новых паведамленняў</item>
-      <item quantity="other">%d новага паведамлення</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Прайграць"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Пазначыць як прачытанае"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Паўтараць"</string>
-    <string name="action_reply" msgid="564106590567600685">"Адказаць"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Спыніць"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Закрыць"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Не ўдалося адправіць адказ. Паўтарыце спробу."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Не ўдалося адправіць адказ. Прылада не падключана."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s кажа"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Не ўдалося зачытаць паведамленне."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Адказ адпраўлены кантакту %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Імя недаступнае"</string>
-</resources>
diff --git a/car-messenger-common/res/values-bg/strings.xml b/car-messenger-common/res/values-bg/strings.xml
deleted file mode 100644
index 911ea86..0000000
--- a/car-messenger-common/res/values-bg/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d нови съобщения</item>
-      <item quantity="one">Ново съобщение</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Възпроизвеждане"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Означаване като прочетено"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Повтаряне"</string>
-    <string name="action_reply" msgid="564106590567600685">"Отговор"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Спиране"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Затваряне"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Изпращането на отговора не бе успешно. Моля, опитайте отново."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Изпращането на отговора не бе успешно. Устройството не е свързано."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s казва"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Съобщението не може да бъде прочетено."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Отговорът бе изпратен до %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Няма име"</string>
-</resources>
diff --git a/car-messenger-common/res/values-bn/strings.xml b/car-messenger-common/res/values-bn/strings.xml
deleted file mode 100644
index bf17122..0000000
--- a/car-messenger-common/res/values-bn/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%dটি নতুন মেসেজ</item>
-      <item quantity="other">%dটি নতুন মেসেজ</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"চালান"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"\'পড়া হয়েছে\' হিসেবে চিহ্নিত করুন"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"পুনরাবৃত্তি করুন"</string>
-    <string name="action_reply" msgid="564106590567600685">"উত্তর"</string>
-    <string name="action_stop" msgid="6950369080845695405">"থামান"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"বন্ধ করুন"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"উত্তর পাঠানো যায়নি। আবার চেষ্টা করুন।"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"উত্তর পাঠানো যায়নি। ডিভাইস কানেক্ট করা নেই।"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s বলছে"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"মেসেজ পড়া যাচ্ছে না।"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s-এ উত্তর পাঠানো হয়েছে"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"নাম উপলভ্য নেই"</string>
-</resources>
diff --git a/car-messenger-common/res/values-bs/strings.xml b/car-messenger-common/res/values-bs/strings.xml
deleted file mode 100644
index 544296a..0000000
--- a/car-messenger-common/res/values-bs/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nova poruka</item>
-      <item quantity="few">%d nove poruke</item>
-      <item quantity="other">%d novih poruka</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reproduciraj"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označi kao pročitano"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ponovi"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odgovori"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zaustavi"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zatvori"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nije moguće poslati odgovor. Pokušajte ponovo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nije moguće poslati odgovor. Uređaj nije povezan."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s kaže"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nije moguće pročitati poruku naglas."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odgovor je poslan kontaktu %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ime nije dostupno"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ca/strings.xml b/car-messenger-common/res/values-ca/strings.xml
deleted file mode 100644
index 09174e7..0000000
--- a/car-messenger-common/res/values-ca/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d missatges nous</item>
-      <item quantity="one">Missatge nou</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reprodueix"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marca com a llegit"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repeteix"</string>
-    <string name="action_reply" msgid="564106590567600685">"Respon"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Atura"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Tanca"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"No s\'ha pogut enviar la resposta. Torna-ho a provar."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"No s\'ha pogut enviar la resposta. El dispositiu no està connectat."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s diu"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"No es pot llegir el missatge en veu alta."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"S\'ha enviat la resposta a %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nom no disponible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-cs/strings.xml b/car-messenger-common/res/values-cs/strings.xml
deleted file mode 100644
index bd4c28a..0000000
--- a/car-messenger-common/res/values-cs/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="few">%d nové zprávy</item>
-      <item quantity="many">%d nové zprávy</item>
-      <item quantity="other">%d nových zpráv</item>
-      <item quantity="one">Nová zpráva</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Přehrát"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označit jako přečtené"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Opakování"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odpovědět"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zastavit"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zavřít"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Odpověď se nepodařilo odeslat. Zkuste to znovu."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Odpověď se nepodařilo odeslat. Zařízení není připojeno."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s říká"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Zprávu se nepodařilo přečíst."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odpověď odeslána příjemci %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Jméno není dostupné"</string>
-</resources>
diff --git a/car-messenger-common/res/values-da/strings.xml b/car-messenger-common/res/values-da/strings.xml
deleted file mode 100644
index f18e544..0000000
--- a/car-messenger-common/res/values-da/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d ny besked</item>
-      <item quantity="other">%d nye beskeder</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Afspil"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Markér som læst"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Gentag"</string>
-    <string name="action_reply" msgid="564106590567600685">"Svar"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Luk"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Svaret kunne ikke sendes. Prøv igen."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Svaret kunne ikke sendes. Enheden er ikke tilsluttet."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s siger"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Beskeden kan ikke læses højt."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Svaret er sendt til %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Navnet er ikke tilgængeligt"</string>
-</resources>
diff --git a/car-messenger-common/res/values-de/strings.xml b/car-messenger-common/res/values-de/strings.xml
deleted file mode 100644
index 274b1f1..0000000
--- a/car-messenger-common/res/values-de/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d neue Nachrichten</item>
-      <item quantity="one">Neue Nachricht</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Wiedergeben"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Als gelesen markieren"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Wiederholen"</string>
-    <string name="action_reply" msgid="564106590567600685">"Antworten"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Abbrechen"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Schließen"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Antwort kann nicht gesendet werden. Bitte versuch es noch einmal."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Antwort kann nicht gesendet werden. Gerät ist nicht verbunden."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s sagt"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Die Nachricht kann nicht vorgelesen werden."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Antwort wurde an %s gesendet"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Name nicht verfügbar"</string>
-</resources>
diff --git a/car-messenger-common/res/values-el/strings.xml b/car-messenger-common/res/values-el/strings.xml
deleted file mode 100644
index 1429ba4..0000000
--- a/car-messenger-common/res/values-el/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d νέα μηνύματα</item>
-      <item quantity="one">Νέο μήνυμα</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Αναπαραγωγή"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Επισήμανση ως αναγνωσμένο"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Επανάληψη"</string>
-    <string name="action_reply" msgid="564106590567600685">"Απάντηση"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Διακοπή"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Κλείσιμο"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Δεν είναι δυνατή η αποστολή απάντησης. Δοκιμάστε ξανά."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Δεν είναι δυνατή η αποστολή απάντησης. Η συσκευή δεν είναι συνδεδεμένη."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"Ο χρήστης %s λέει"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Δεν είναι δυνατή η ανάγνωση του μηνύματος."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"%s"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Η απάντηση στάλθηκε στον χρήστη %s."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Το όνομα δεν είναι διαθέσιμο."</string>
-</resources>
diff --git a/car-messenger-common/res/values-en-rAU/strings.xml b/car-messenger-common/res/values-en-rAU/strings.xml
deleted file mode 100644
index e50266e..0000000
--- a/car-messenger-common/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d new messages</item>
-      <item quantity="one">New message</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Play"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Mark as read"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repeat"</string>
-    <string name="action_reply" msgid="564106590567600685">"Reply"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Close"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Unable to send reply. Please try again."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Unable to send reply. Device is not connected."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s says"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Can\'t read out message."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\'%s\'"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Reply sent to %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Name not available"</string>
-</resources>
diff --git a/car-messenger-common/res/values-en-rCA/strings.xml b/car-messenger-common/res/values-en-rCA/strings.xml
deleted file mode 100644
index e50266e..0000000
--- a/car-messenger-common/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d new messages</item>
-      <item quantity="one">New message</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Play"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Mark as read"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repeat"</string>
-    <string name="action_reply" msgid="564106590567600685">"Reply"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Close"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Unable to send reply. Please try again."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Unable to send reply. Device is not connected."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s says"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Can\'t read out message."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\'%s\'"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Reply sent to %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Name not available"</string>
-</resources>
diff --git a/car-messenger-common/res/values-en-rGB/strings.xml b/car-messenger-common/res/values-en-rGB/strings.xml
deleted file mode 100644
index e50266e..0000000
--- a/car-messenger-common/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d new messages</item>
-      <item quantity="one">New message</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Play"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Mark as read"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repeat"</string>
-    <string name="action_reply" msgid="564106590567600685">"Reply"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Close"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Unable to send reply. Please try again."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Unable to send reply. Device is not connected."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s says"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Can\'t read out message."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\'%s\'"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Reply sent to %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Name not available"</string>
-</resources>
diff --git a/car-messenger-common/res/values-en-rIN/strings.xml b/car-messenger-common/res/values-en-rIN/strings.xml
deleted file mode 100644
index e50266e..0000000
--- a/car-messenger-common/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d new messages</item>
-      <item quantity="one">New message</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Play"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Mark as read"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repeat"</string>
-    <string name="action_reply" msgid="564106590567600685">"Reply"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stop"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Close"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Unable to send reply. Please try again."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Unable to send reply. Device is not connected."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s says"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Can\'t read out message."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\'%s\'"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Reply sent to %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Name not available"</string>
-</resources>
diff --git a/car-messenger-common/res/values-en-rXC/strings.xml b/car-messenger-common/res/values-en-rXC/strings.xml
deleted file mode 100644
index 99a3fb8..0000000
--- a/car-messenger-common/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎%d new messages‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎New message‎‏‎‎‏‎</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎Play‎‏‎‎‏‎"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎Mark As Read‎‏‎‎‏‎"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎Repeat‎‏‎‎‏‎"</string>
-    <string name="action_reply" msgid="564106590567600685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎Reply‎‏‎‎‏‎"</string>
-    <string name="action_stop" msgid="6950369080845695405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎Stop‎‏‎‎‏‎"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎Close‎‏‎‎‏‎"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎Unable to send reply. Please try again.‎‏‎‎‏‎"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎Unable to send reply. Device is not connected.‎‏‎‎‏‎"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎%s says‎‏‎‎‏‎"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎Can\'t read out message.‎‏‎‎‏‎"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‎\"%s\"‎‏‎‎‏‎"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‎Reply sent to %s‎‏‎‎‏‎"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎Name not available‎‏‎‎‏‎"</string>
-</resources>
diff --git a/car-messenger-common/res/values-es-rUS/strings.xml b/car-messenger-common/res/values-es-rUS/strings.xml
deleted file mode 100644
index bfd0c97..0000000
--- a/car-messenger-common/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mensajes nuevos</item>
-      <item quantity="one">Mensaje nuevo</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reproducir"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcar como leído"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetir"</string>
-    <string name="action_reply" msgid="564106590567600685">"Responder"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Detener"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Cerrar"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"No se pudo enviar la respuesta. Vuelve a intentarlo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"No se pudo enviar la respuesta. El dispositivo no está conectado."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dice"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"No se puede leer en voz alta el mensaje."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Se envió la respuesta a %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nombre no disponible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-es/strings.xml b/car-messenger-common/res/values-es/strings.xml
deleted file mode 100644
index 0a66752..0000000
--- a/car-messenger-common/res/values-es/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mensajes nuevos</item>
-      <item quantity="one">Mensaje nuevo</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reproducir"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcar como leído"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetir"</string>
-    <string name="action_reply" msgid="564106590567600685">"Responder"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Detener"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Cerrar"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"No se ha podido enviar la respuesta. Inténtalo de nuevo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"No se ha podido enviar la respuesta. El dispositivo no está conectado."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dice"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"No se puede leer el mensaje en voz alta."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Se ha enviado la respuesta a %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"El nombre no está disponible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-et/strings.xml b/car-messenger-common/res/values-et/strings.xml
deleted file mode 100644
index e4167f6..0000000
--- a/car-messenger-common/res/values-et/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d uut sõnumit</item>
-      <item quantity="one">Uus sõnum</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Esita"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Märgi loetuks"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Korda"</string>
-    <string name="action_reply" msgid="564106590567600685">"Vasta"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Peata"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Sule"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Vastust ei saa saata. Proovige uuesti."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Vastust ei saa saata. Seade pole ühendatud."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ütleb"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Sõnumit ei saa ette lugeda."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Vastus saadeti üksusele %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nimi pole saadaval"</string>
-</resources>
diff --git a/car-messenger-common/res/values-eu/strings.xml b/car-messenger-common/res/values-eu/strings.xml
deleted file mode 100644
index 7a5c20e..0000000
--- a/car-messenger-common/res/values-eu/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mezu berri</item>
-      <item quantity="one">Mezu berria</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Erreproduzitu"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Markatu irakurritako gisa"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Errepikatu"</string>
-    <string name="action_reply" msgid="564106590567600685">"Erantzun"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Gelditu"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Itxi"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Ezin da bidali erantzuna. Saiatu berriro."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Ezin da bidali erantzuna. Gailua ez dago konektatuta."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s erabiltzaileak hau dio:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Ezin da irakurri ozen mezua."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Erantzuna bidali zaio %s erabiltzaileari"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Izena ez dago erabilgarri"</string>
-</resources>
diff --git a/car-messenger-common/res/values-fa/strings.xml b/car-messenger-common/res/values-fa/strings.xml
deleted file mode 100644
index 71fc229..0000000
--- a/car-messenger-common/res/values-fa/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d پیام جدید</item>
-      <item quantity="other">%d پیام جدید</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"پخش"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"علامت‌گذاری به‌عنوان خوانده‌شده"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"تکرار"</string>
-    <string name="action_reply" msgid="564106590567600685">"پاسخ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"توقف"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"بستن"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"پاسخ ارسال نشد. لطفاً دوباره امتحان کنید."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"پاسخ ارسال نشد. دستگاه متصل نشده است."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s می‌گوید"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"خواندن پیام ممکن نیست."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"«%s»"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"پاسخ برای %s ارسال شد"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"نام در دسترس نیست"</string>
-</resources>
diff --git a/car-messenger-common/res/values-fi/strings.xml b/car-messenger-common/res/values-fi/strings.xml
deleted file mode 100644
index 4e905c2..0000000
--- a/car-messenger-common/res/values-fi/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d uutta viestiä</item>
-      <item quantity="one">Uusi viesti</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Toista"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Merkitse luetuksi"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Toista uudelleen"</string>
-    <string name="action_reply" msgid="564106590567600685">"Vastaa"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Lopeta"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Sulje"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Vastaaminen ei onnistunut. Yritä uudelleen."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Vastaaminen ei onnistunut. Laite ei saa yhteyttä."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s sanoo"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Viestiä ei voi lukea."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Vastaus lähetetty: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nimi ei ole saatavilla"</string>
-</resources>
diff --git a/car-messenger-common/res/values-fr-rCA/strings.xml b/car-messenger-common/res/values-fr-rCA/strings.xml
deleted file mode 100644
index 56086e6..0000000
--- a/car-messenger-common/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nouveau message</item>
-      <item quantity="other">%d nouveaux messages</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Faire jouer"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marquer comme lu"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Répéter"</string>
-    <string name="action_reply" msgid="564106590567600685">"Répondre"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Arrêter"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Fermer"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Impossible d\'envoyer la réponse. Veuillez réessayer."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Impossible d\'envoyer la réponse. L\'appareil n\'est pas connecté."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dit"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Impossible de lire le message."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"« %s »"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Réponse envoyée à %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nom indisponible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-fr/strings.xml b/car-messenger-common/res/values-fr/strings.xml
deleted file mode 100644
index a96b14f..0000000
--- a/car-messenger-common/res/values-fr/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nouveau message</item>
-      <item quantity="other">%d nouveaux messages</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Lire"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marquer comme lu"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Répéter"</string>
-    <string name="action_reply" msgid="564106590567600685">"Répondre"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Arrêter"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Fermer"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Impossible d\'envoyer la réponse. Veuillez réessayer."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Impossible d\'envoyer la réponse. L\'appareil n\'est pas connecté."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dit"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Impossible de lire le message à haute voix."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Réponse envoyée à %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nom indisponible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-gl/strings.xml b/car-messenger-common/res/values-gl/strings.xml
deleted file mode 100644
index b81c8e7..0000000
--- a/car-messenger-common/res/values-gl/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mensaxes novas</item>
-      <item quantity="one">Mensaxe nova</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reproducir"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcar como lido"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetir"</string>
-    <string name="action_reply" msgid="564106590567600685">"Responder"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Deter"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Pechar"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Non se puido enviar a resposta. Téntao de novo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Non se puido enviar a resposta. O dispositivo non está conectado."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s di"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Non se puido ler a mensaxe."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Enviouse a resposta a %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"O nome non está dispoñible"</string>
-</resources>
diff --git a/car-messenger-common/res/values-gu/strings.xml b/car-messenger-common/res/values-gu/strings.xml
deleted file mode 100644
index 3871966..0000000
--- a/car-messenger-common/res/values-gu/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d નવો સંદેશ</item>
-      <item quantity="other">%d નવા સંદેશા</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ચલાવો"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"વાંચેલાં તરીકે ચિહ્નિત કરો"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"પુનરાવર્તન"</string>
-    <string name="action_reply" msgid="564106590567600685">"જવાબ આપો"</string>
-    <string name="action_stop" msgid="6950369080845695405">"રોકો"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"બંધ કરો"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"જવાબ મોકલી શકતા નથી. કૃપા કરી ફરી પ્રયાસ કરો."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"જવાબ મોકલી શકતા નથી. ડિવાઇસ કનેક્ટ થયું નથી."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s કહે છે"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"સંદેશ વાંચી શકાતો નથી."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s પર જવાબ મોકલ્યો છે"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"નામ ઉપલબ્ધ નથી"</string>
-</resources>
diff --git a/car-messenger-common/res/values-hi/strings.xml b/car-messenger-common/res/values-hi/strings.xml
deleted file mode 100644
index 74a5693..0000000
--- a/car-messenger-common/res/values-hi/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d नए मैसेज</item>
-      <item quantity="other">%d नए मैसेज</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"चलाएं"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"\'पढ़ा गया\' का निशान लगाएं"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"दोहराएं"</string>
-    <string name="action_reply" msgid="564106590567600685">"जवाब दें"</string>
-    <string name="action_stop" msgid="6950369080845695405">"रोकें"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"बंद करें"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"जवाब नहीं भेजा जा सका. कृपया फिर से कोशिश करें."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"जवाब नहीं भेजा जा सका. डिवाइस कनेक्ट नहीं है."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s का मैसेज यह है"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"मैसेज पढ़ा नहीं जा सकता."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s को जवाब भेजा गया"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"नाम उपलब्ध नहीं है"</string>
-</resources>
diff --git a/car-messenger-common/res/values-hr/strings.xml b/car-messenger-common/res/values-hr/strings.xml
deleted file mode 100644
index 8a46b5d..0000000
--- a/car-messenger-common/res/values-hr/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nova poruka</item>
-      <item quantity="few">%d nove poruke</item>
-      <item quantity="other">%d novih poruka</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Pokreni"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označi kao pročitano"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ponovi"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odgovori"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zaustavi"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zatvori"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Slanje odgovora nije uspjelo. Pokušajte ponovo."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Slanje odgovora nije uspjelo. Uređaj nije povezan."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s kaže"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nije moguće pročitati poruku naglas."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odgovor je poslan kontaktu %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ime nije dostupno"</string>
-</resources>
diff --git a/car-messenger-common/res/values-hu/strings.xml b/car-messenger-common/res/values-hu/strings.xml
deleted file mode 100644
index c6d7693..0000000
--- a/car-messenger-common/res/values-hu/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d új üzenet</item>
-      <item quantity="one">Új üzenet</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Lejátszás"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Megjelölés olvasottként"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ismétlés"</string>
-    <string name="action_reply" msgid="564106590567600685">"Válasz"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Leállítás"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Bezárás"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nem sikerült a válasz elküldése. Próbálja újra."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nem sikerült a válasz elküldése. Az eszköz nincs csatlakoztatva."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s a következőt küldte:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Az üzenet felolvasása nem sikerült."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Válasz elküldve a következőnek: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"A név nem használható"</string>
-</resources>
diff --git a/car-messenger-common/res/values-hy/strings.xml b/car-messenger-common/res/values-hy/strings.xml
deleted file mode 100644
index c6faa75..0000000
--- a/car-messenger-common/res/values-hy/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d նոր հաղորդագրություն</item>
-      <item quantity="other">%d նոր հաղորդագրություն</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Նվագարկել"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Նշել որպես կարդացված"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Կրկնել"</string>
-    <string name="action_reply" msgid="564106590567600685">"Պատասխանել"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Դադարեցնել"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Փակել"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Չհաջողվեց ուղարկել պատասխանը։ Նորից փորձեք:"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Չհաջողվեց ուղարկել պատասխանը։ Սարքը միացված չէ։"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"Հաղորդագրություն %s-ից․"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Չհաջողվեց ընթերցել հաղորդագրությունը։"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"«%s»"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Պատասխանն ուղարկվեց %s-ին"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Անունը հասանելի չէ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-in/strings.xml b/car-messenger-common/res/values-in/strings.xml
deleted file mode 100644
index 12182d4..0000000
--- a/car-messenger-common/res/values-in/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d pesan baru</item>
-      <item quantity="one">Pesan baru</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Putar"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Tandai Telah Dibaca"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ulangi"</string>
-    <string name="action_reply" msgid="564106590567600685">"Balas"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Berhenti"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Tutup"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Tidak dapat mengirim balasan. Coba lagi."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Tidak dapat mengirim balasan. Perangkat tidak terhubung"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s mengatakan"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Tidak dapat membacakan pesan."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Jawaban dikirim ke %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nama tidak tersedia"</string>
-</resources>
diff --git a/car-messenger-common/res/values-is/strings.xml b/car-messenger-common/res/values-is/strings.xml
deleted file mode 100644
index 079b6d5..0000000
--- a/car-messenger-common/res/values-is/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d ný skilaboð</item>
-      <item quantity="other">%d ný skilaboð</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Spila"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Merkja sem lesið"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Endurtaka"</string>
-    <string name="action_reply" msgid="564106590567600685">"Svara"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stöðva"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Loka"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Ekki tókst að senda svar. Reyndu aftur."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Ekki tókst að senda svar. Tækið er ekki tengt."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s segir"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Ekki er hægt að lesa upp skilaboð."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Svar sent til %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nafnið er ekki tiltækt"</string>
-</resources>
diff --git a/car-messenger-common/res/values-it/strings.xml b/car-messenger-common/res/values-it/strings.xml
deleted file mode 100644
index a1f750d..0000000
--- a/car-messenger-common/res/values-it/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d nuovi messaggi</item>
-      <item quantity="one">Nuovo messaggio</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Riproduci"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Segna come letto"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ripeti"</string>
-    <string name="action_reply" msgid="564106590567600685">"Rispondi"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Interrompi"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Chiudi"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Impossibile inviare la risposta. Riprova."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Impossibile inviare la risposta. Il dispositivo non è collegato."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dice"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Impossibile leggere il messaggio ad alta voce."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Risposta inviata a %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nome non disponibile"</string>
-</resources>
diff --git a/car-messenger-common/res/values-iw/strings.xml b/car-messenger-common/res/values-iw/strings.xml
deleted file mode 100644
index c28823d..0000000
--- a/car-messenger-common/res/values-iw/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="two">‎%d‎ הודעות חדשות</item>
-      <item quantity="many">‎%d‎ הודעות חדשות</item>
-      <item quantity="other">‎%d‎ הודעות חדשות</item>
-      <item quantity="one">הודעה חדשה</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"הפעלה"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"סימון כפריט שנקרא"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"חזרה"</string>
-    <string name="action_reply" msgid="564106590567600685">"שליחת תשובה"</string>
-    <string name="action_stop" msgid="6950369080845695405">"עצירה"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"סגירה"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"לא ניתן לשלוח תשובה. יש לנסות שוב."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"לא ניתן לשלוח תשובה. המכשיר לא מחובר."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s אומר/ת"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"לא ניתן להקריא את ההודעה."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"התשובה נשלחה אל %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"השם לא זמין"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ja/strings.xml b/car-messenger-common/res/values-ja/strings.xml
deleted file mode 100644
index 6393b4f..0000000
--- a/car-messenger-common/res/values-ja/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d 件の新着メッセージ</item>
-      <item quantity="one">新着メッセージ</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"再生"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"既読にする"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"繰り返し"</string>
-    <string name="action_reply" msgid="564106590567600685">"返信"</string>
-    <string name="action_stop" msgid="6950369080845695405">"停止"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"閉じる"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"返信できませんでした。もう一度お試しください。"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"返信できませんでした。デバイスが接続されていません。"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s さんからのメッセージです"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"メッセージを読み上げられません。"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"「%s」"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s さんに返信しました"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"名前がありません"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ka/strings.xml b/car-messenger-common/res/values-ka/strings.xml
deleted file mode 100644
index 29c46f3..0000000
--- a/car-messenger-common/res/values-ka/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d ახალი შეტყობინება</item>
-      <item quantity="one">ახალი შეტყობინება</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"დაკვრა"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"წაკითხულად მონიშვნა"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"გამეორება"</string>
-    <string name="action_reply" msgid="564106590567600685">"პასუხი"</string>
-    <string name="action_stop" msgid="6950369080845695405">"შეწყვეტა"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"დახურვა"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"პასუხის გაგზავნა ვერ მოხერხდა. გთხოვთ, ცადოთ ხელახლა."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"პასუხის გაგზავნა ვერ მოხერხდა. მოწყობილობა დაკავშირებული არ არის."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ამბობს"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"შეტყობინების ხმამაღლა წაკითხვა ვერ ხერხდება."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"პასუხი გაეგზავნა %s-ს"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"სახელი მიუწვდომელია"</string>
-</resources>
diff --git a/car-messenger-common/res/values-kk/strings.xml b/car-messenger-common/res/values-kk/strings.xml
deleted file mode 100644
index 9132ef2..0000000
--- a/car-messenger-common/res/values-kk/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d жаңа хабар</item>
-      <item quantity="one">Жаңа хабар</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Ойнату"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Оқылды деп белгілеу"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Қайталау"</string>
-    <string name="action_reply" msgid="564106590567600685">"Жауап беру"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Тоқтату"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Жабу"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Жауап жіберілмеді. Қайталап көріңіз."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Жауап жіберілмеді. Құрылғы жалғанбаған."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s дейді"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Хабар оқылмады."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Жауап %s атты пайдаланушыға жіберілді."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Атауы жоқ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-km/strings.xml b/car-messenger-common/res/values-km/strings.xml
deleted file mode 100644
index b060c99..0000000
--- a/car-messenger-common/res/values-km/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">សារ​ថ្មី %d</item>
-      <item quantity="one">សារ​ថ្មី</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ចាក់"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"សម្គាល់​ថា​បានអាន​ហើយ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ធ្វើ​ឡើងវិញ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ឆ្លើយតប"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ឈប់"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"បិទ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"មិនអាច​ផ្ញើ​ការឆ្លើយតប​បានទេ​។ សូមព្យាយាមម្ដងទៀត។"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"មិនអាច​ផ្ញើ​ការឆ្លើយតប​បានទេ​។ មិន​បាន​ភ្ជាប់​ឧបករណ៍​ទេ​។"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s និយាយ​ថា"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"មិនអាច​អានសារឱ្យឮៗ​បានទេ។"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"ការ​ឆ្លើយតប​ដែលបាន​ផ្ញើ​ទៅ %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"មិន​មាន​ឈ្មោះ​ទេ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-kn/strings.xml b/car-messenger-common/res/values-kn/strings.xml
deleted file mode 100644
index 4b89c85..0000000
--- a/car-messenger-common/res/values-kn/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d ಹೊಸ ಸಂದೇಶಗಳು</item>
-      <item quantity="other">%d ಹೊಸ ಸಂದೇಶಗಳು</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ಪ್ಲೇ"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ಓದಲಾಗಿದೆ ಎಂದು ಗುರುತಿಸಿ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ಪುನರಾವರ್ತನೆ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ಪ್ರತ್ಯುತ್ತರಿಸಿ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ನಿಲ್ಲಿಸಿ"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ಮುಚ್ಚಿರಿ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ಪ್ರತ್ಯುತ್ತರ ಕಳುಹಿಸಲು ವಿಫಲವಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ಪ್ರತ್ಯುತ್ತರ ಕಳುಹಿಸಲು ವಿಫಲವಾಗಿದೆ. ಸಾಧನವು ಕನೆಕ್ಟ್ ಆಗಿಲ್ಲ."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ಹೇಳುತ್ತದೆ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"ಸಂದೇಶವನ್ನು ಓದಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s ಗೆ ಪ್ರತ್ಯುತ್ತರ ಕಳುಹಿಸಲಾಗಿದೆ"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ಹೆಸರು ಲಭ್ಯವಿಲ್ಲ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ko/strings.xml b/car-messenger-common/res/values-ko/strings.xml
deleted file mode 100644
index 1264304..0000000
--- a/car-messenger-common/res/values-ko/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">새 메시지 %d개</item>
-      <item quantity="one">새 메시지</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"재생"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"읽은 상태로 표시"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"반복"</string>
-    <string name="action_reply" msgid="564106590567600685">"답장"</string>
-    <string name="action_stop" msgid="6950369080845695405">"중지"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"닫기"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"답장을 보낼 수 없습니다. 다시 시도해 보세요."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"답장을 보낼 수 없습니다. 기기가 연결되어 있지 않습니다."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s님의 말입니다"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"메시지를 소리 내어 읽을 수 없습니다."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"’%s’"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s님에게 답장을 전송했습니다."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"이름을 사용할 수 없습니다."</string>
-</resources>
diff --git a/car-messenger-common/res/values-ky/strings.xml b/car-messenger-common/res/values-ky/strings.xml
deleted file mode 100644
index 4d33c71..0000000
--- a/car-messenger-common/res/values-ky/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d жаңы билдирүү</item>
-      <item quantity="one">Жаңы билдирүү</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Ойнотуу"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Окулду деп белгилөө"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Кайталоо"</string>
-    <string name="action_reply" msgid="564106590567600685">"Жооп берүү"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Токтотуу"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Жабуу"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Жооп жөнөтүлгөн жок. Кайталап көрүңүз."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Жооп жөнөтүлгөн жок. Түзмөк туташкан жок."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s төмөнкүнү айтты:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Билдирүү окулбай жатат."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Жооп төмөнкүгө жөнөтүлдү: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Аты-жөнү жеткиликсиз"</string>
-</resources>
diff --git a/car-messenger-common/res/values-lo/strings.xml b/car-messenger-common/res/values-lo/strings.xml
deleted file mode 100644
index db6627d..0000000
--- a/car-messenger-common/res/values-lo/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d ຂໍ້ຄວາມໃໝ່</item>
-      <item quantity="one">ຂໍ້ຄວາມໃໝ່</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ຫຼິ້ນ"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ໝາຍວ່າອ່ານແລ້ວ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ເຮັດຊໍ້າຄືນ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ຕອບກັບ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ຢຸດ"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ປິດ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ບໍ່ສາມາດສົ່ງການຕອບກັບໄດ້. ກະລຸນາລອງໃໝ່."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ບໍ່ສາມາດສົ່ງການຕອບກັບໄດ້. ອຸປະກອນບໍ່ໄດ້ເຊື່ອມຕໍ່."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ເວົ້າວ່າ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"ບໍ່ສາມາດອ່ານອອກສຽງຂໍ້ຄວາມໄດ້."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"ສົ່ງການຕອບກັບຫາ %s ແລ້ວ"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ຊື່ບໍ່ສາມາດໃຊ້ໄດ້"</string>
-</resources>
diff --git a/car-messenger-common/res/values-lt/strings.xml b/car-messenger-common/res/values-lt/strings.xml
deleted file mode 100644
index e9ba6cb..0000000
--- a/car-messenger-common/res/values-lt/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d naujas pranešimas</item>
-      <item quantity="few">%d nauji pranešimai</item>
-      <item quantity="many">%d naujo pranešimo</item>
-      <item quantity="other">%d naujų pranešimų</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Leisti"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Pažymėti kaip skaitytą"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Kartoti"</string>
-    <string name="action_reply" msgid="564106590567600685">"Atsakyti"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Sustabdyti"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Uždaryti"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nepavyko išsiųsti atsakymo. Bandykite dar kartą."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nepavyko išsiųsti atsakymo. Įrenginys neprijungtas."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s sako"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nepavyksta perskaityti pranešimo."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Atsakymas išsiųstas %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Vardas nepasiekiamas"</string>
-</resources>
diff --git a/car-messenger-common/res/values-lv/strings.xml b/car-messenger-common/res/values-lv/strings.xml
deleted file mode 100644
index c6990ec..0000000
--- a/car-messenger-common/res/values-lv/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="zero">%d jauni ziņojumi</item>
-      <item quantity="one">%d jauns ziņojums</item>
-      <item quantity="other">%d jauni ziņojumi</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Atskaņot"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Atzīmēt kā izlasītu"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Atkārtot"</string>
-    <string name="action_reply" msgid="564106590567600685">"Atbildēt"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Apturēt"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Aizvērt"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nevar nosūtīt atbildi. Mēģiniet vēlreiz."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nevar nosūtīt atbildi. Ierīce nav pievienota."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s saka"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nevar nolasīt ziņojumu."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"“%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Atbilde nosūtīta lietotājam %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Vārds nav pieejams."</string>
-</resources>
diff --git a/car-messenger-common/res/values-mk/strings.xml b/car-messenger-common/res/values-mk/strings.xml
deleted file mode 100644
index 12da0be..0000000
--- a/car-messenger-common/res/values-mk/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d нова порака</item>
-      <item quantity="other">%d нови пораки</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Пушти"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Означи како прочитано"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Повтори"</string>
-    <string name="action_reply" msgid="564106590567600685">"Одговори"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Сопри"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Затвори"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Не може да се испрати одговор. Обидете се повторно."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Не може да се испрати одговор. Уредот не е поврзан."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s вели"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Не може да се прочита пораката на глас."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Одговорот е испратен до %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Името не е достапно"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ml/strings.xml b/car-messenger-common/res/values-ml/strings.xml
deleted file mode 100644
index d154f97..0000000
--- a/car-messenger-common/res/values-ml/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d പുതിയ സന്ദേശങ്ങൾ</item>
-      <item quantity="one">പുതിയ സന്ദേശം</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"പ്ലേ ചെയ്യുക"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"വായിച്ചതായി അടയാളപ്പെടുത്തുക"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ആവർത്തിക്കുക"</string>
-    <string name="action_reply" msgid="564106590567600685">"മറുപടി നൽകുക"</string>
-    <string name="action_stop" msgid="6950369080845695405">"നിർത്തുക"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"അടയ്ക്കുക"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"മറുപടി അയയ്ക്കാനാവുന്നില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"മറുപടി അയയ്ക്കാനാവുന്നില്ല. ഉപകരണം കണക്റ്റ് ചെയ്തിട്ടില്ല."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s പറയുന്നു"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"സന്ദേശം ഉറക്കെ വായിക്കാനാവില്ല."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s എന്നതിലേക്ക് മറുപടി അയച്ചു"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"പേര് ലഭ്യമല്ല"</string>
-</resources>
diff --git a/car-messenger-common/res/values-mn/strings.xml b/car-messenger-common/res/values-mn/strings.xml
deleted file mode 100644
index 0cd0bcc..0000000
--- a/car-messenger-common/res/values-mn/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d шинэ мессеж</item>
-      <item quantity="one">Шинэ мессеж</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Тоглуулах"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Уншсан гэж тэмдэглэх"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Давтах"</string>
-    <string name="action_reply" msgid="564106590567600685">"Хариу бичих"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Зогсоох"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Хаах"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Хариу илгээх боломжгүй байна. Дахин оролдоно уу."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Хариу илгээх боломжгүй байна. Төхөөрөмж холбогдоогүй байна."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s хэлж байна"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Мессежийг унших боломжгүй байна."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s-д хариу илгээсэн"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Нэр ашиглалтад алга"</string>
-</resources>
diff --git a/car-messenger-common/res/values-mr/strings.xml b/car-messenger-common/res/values-mr/strings.xml
deleted file mode 100644
index d81f6e9..0000000
--- a/car-messenger-common/res/values-mr/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d नवीन मेसेज</item>
-      <item quantity="one">नवीन मेसेज</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"प्ले करा"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"वाचलेले म्हणून खूण करा"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"रिपीट करा"</string>
-    <string name="action_reply" msgid="564106590567600685">"उतर द्या"</string>
-    <string name="action_stop" msgid="6950369080845695405">"थांबा"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"बंद करा"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"उत्तर पाठवता आले नाही. कृपया पुन्हा प्रयत्न करा."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"उत्तर पाठवता आले नाही. डिव्हाइस कनेक्ट केलेले नाही."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s म्हणतात"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"मेसेज वाचू शकत नाही."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"उत्तर %s ला पाठवले"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"नाव उपलब्ध नाही"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ms/strings.xml b/car-messenger-common/res/values-ms/strings.xml
deleted file mode 100644
index ff4041c..0000000
--- a/car-messenger-common/res/values-ms/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mesej baharu</item>
-      <item quantity="one">Mesej baharu</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Main"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Tandai Sebagai Dibaca"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ulang"</string>
-    <string name="action_reply" msgid="564106590567600685">"Balas"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Berhenti"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Tutup"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Tidak dapat menghantar balasan. Sila cuba lagi."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Tidak dapat menghantar balasan. Peranti tidak disambungkan."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s mengatakan"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Tidak dapat membaca mesej dengan kuat."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Balasan dihantar kepada %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nama tidak tersedia"</string>
-</resources>
diff --git a/car-messenger-common/res/values-my/strings.xml b/car-messenger-common/res/values-my/strings.xml
deleted file mode 100644
index 9604313..0000000
--- a/car-messenger-common/res/values-my/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">မက်ဆေ့ဂျ်အသစ် %d စောင်</item>
-      <item quantity="one">မက်ဆေ့ဂျ်အသစ်</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ဖွင့်ရန်"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ဖတ်ပြီးဟု မှတ်သားရန်"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ထပ်လုပ်ရန်"</string>
-    <string name="action_reply" msgid="564106590567600685">"စာပြန်ရန်"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ရပ်ရန်"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ပိတ်ရန်"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ပြန်စာကို ပို့၍မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ပြန်စာကို ပို့၍မရပါ။ စက်ကို ကွန်ရက်ချိတ်မထားပါ။"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ကပြောသည်မှာ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"မက်ဆေ့ဂျ်ကို အသံထွက်ဖတ်၍မရပါ။"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"ပြန်စာကို %s သို့ ပို့လိုက်ပါပြီ"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"အမည် မရနိုင်ပါ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-nb/strings.xml b/car-messenger-common/res/values-nb/strings.xml
deleted file mode 100644
index 70be831..0000000
--- a/car-messenger-common/res/values-nb/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d nye meldinger</item>
-      <item quantity="one">Ny melding</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Spill av"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Merk som lest"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Gjenta"</string>
-    <string name="action_reply" msgid="564106590567600685">"Svar"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stopp"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Lukk"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Kan ikke sende svaret. Prøv på nytt."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Kan ikke sende svaret. Enheten er ikke tilkoblet."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s sier"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Kan ikke lese opp meldingen."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"«%s»"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Svaret er sendt til %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Navnet er ikke tilgjengelig"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ne/strings.xml b/car-messenger-common/res/values-ne/strings.xml
deleted file mode 100644
index 26e1e7c..0000000
--- a/car-messenger-common/res/values-ne/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d नयाँ सन्देशहरू</item>
-      <item quantity="one">नयाँ सन्देश</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"प्ले गर्नुहोस्"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"पढिसकिएको भनी चिन्ह लगाउनुहोस्"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"दोहोर्‍याउनुहोस्"</string>
-    <string name="action_reply" msgid="564106590567600685">"जवाफ पठाउनुहोस्"</string>
-    <string name="action_stop" msgid="6950369080845695405">"रोक्नुहोस्"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"बन्द गर्नुहोस्"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"जवाफ पठाउन सकिएन। कृपया फेरि प्रयास गर्नुहोस्।"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"जवाफ पठाउन सकिएन। यन्त्र जोडिएको छैन।"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s निम्न कुरा भन्नुहुन्छ:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"सन्देशहरू पढ्न सकिँदैन।"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s मा जवाफ पठाइयो"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"नाम उपलब्ध छैन"</string>
-</resources>
diff --git a/car-messenger-common/res/values-nl/strings.xml b/car-messenger-common/res/values-nl/strings.xml
deleted file mode 100644
index 42aff7a..0000000
--- a/car-messenger-common/res/values-nl/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d nieuwe berichten</item>
-      <item quantity="one">Nieuw bericht</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Afspelen"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Markeren als gelezen"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Herhalen"</string>
-    <string name="action_reply" msgid="564106590567600685">"Beantwoorden"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stoppen"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Sluiten"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Antwoord kan niet worden verstuurd. Probeer het opnieuw."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Antwoord kan niet worden verstuurd. Apparaat is niet verbonden."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s zegt"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Kan bericht niet voorlezen."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\'%s\'"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Antwoord naar %s gestuurd"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Naam niet beschikbaar"</string>
-</resources>
diff --git a/car-messenger-common/res/values-or/strings.xml b/car-messenger-common/res/values-or/strings.xml
deleted file mode 100644
index 168f3ae..0000000
--- a/car-messenger-common/res/values-or/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%dଟି ନୂଆ ମେସେଜ୍</item>
-      <item quantity="one">ନୂଆ ମେସେଜ୍</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ଚଲାନ୍ତୁ"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ପଠିତ ଭାବେ ଚିହ୍ନଟ କରନ୍ତୁ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ପୁନରାବୃତ୍ତି କରନ୍ତୁ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ପ୍ରତ୍ୟୁତ୍ତର କରନ୍ତୁ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ପ୍ରତ୍ୟୁତ୍ତର ପଠାଇବାକୁ ଅସମର୍ଥ। ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ପ୍ରତ୍ୟୁତ୍ତର ପଠାଇବାକୁ ଅସମର୍ଥ। ଡିଭାଇସ୍ ସଂଯୋଗ ହୋଇନାହିଁ।"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s କୁହେ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"ମେସେଜ୍ ପଢ଼ାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%sକୁ ପ୍ରତ୍ୟୁତ୍ତର ପଠାଯାଇଛି"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ନାମ ଉପଲବ୍ଧ ନାହିଁ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-pa/strings.xml b/car-messenger-common/res/values-pa/strings.xml
deleted file mode 100644
index 96799b9..0000000
--- a/car-messenger-common/res/values-pa/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d ਨਵਾਂ ਸੁਨੇਹਾ</item>
-      <item quantity="other">%d ਨਵੇਂ ਸੁਨੇਹੇ</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ਚਲਾਓ"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ਪੜ੍ਹੇ ਵਜੋਂ ਨਿਸ਼ਾਨਦੇਹੀ ਕਰੋ"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"ਦੁਹਰਾਓ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ਜਵਾਬ ਦਿਓ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ਬੰਦ ਕਰੋ"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ਬੰਦ ਕਰੋ"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ਜਵਾਬ ਭੇਜਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ਜਵਾਬ ਭੇਜਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ। ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਹੈ।"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s ਕਹਿੰਦਾ ਹੈ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"ਸੁਨੇਹਾ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ।"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s ਨੂੰ ਜਵਾਬ ਭੇਜਿਆ ਗਿਆ"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ਨਾਮ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-pl/strings.xml b/car-messenger-common/res/values-pl/strings.xml
deleted file mode 100644
index b8df4ec..0000000
--- a/car-messenger-common/res/values-pl/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="few">%d nowe wiadomości</item>
-      <item quantity="many">%d nowych wiadomości</item>
-      <item quantity="other">%d nowej wiadomości</item>
-      <item quantity="one">Nowa wiadomość</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Odtwórz"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Oznacz jako przeczytane"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Powtórz"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odpowiedz"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zatrzymaj"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zamknij"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nie udało się wysłać odpowiedzi. Spróbuj ponownie."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nie udało się wysłać odpowiedzi. Urządzenie nie ma połączenia."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s mówi"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nie mogę odczytać wiadomości."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Wysłano odpowiedź do: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nazwa niedostępna"</string>
-</resources>
diff --git a/car-messenger-common/res/values-pt-rPT/strings.xml b/car-messenger-common/res/values-pt-rPT/strings.xml
deleted file mode 100644
index b0da748..0000000
--- a/car-messenger-common/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d novas mensagens</item>
-      <item quantity="one">Nova mensagem</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Reproduzir"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcar como lida"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetir"</string>
-    <string name="action_reply" msgid="564106590567600685">"Responder"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Parar"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Fechar"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Não foi possível enviar a resposta. Tente novamente."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Não foi possível enviar a resposta. O dispositivo não está ligado."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s diz"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Não é possível ler a mensagem em voz alta."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Resposta enviada para %s."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nome não disponível."</string>
-</resources>
diff --git a/car-messenger-common/res/values-pt/strings.xml b/car-messenger-common/res/values-pt/strings.xml
deleted file mode 100644
index 90d2171..0000000
--- a/car-messenger-common/res/values-pt/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d nova mensagem</item>
-      <item quantity="other">%d novas mensagens</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Ouvir"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcar como lida"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetir"</string>
-    <string name="action_reply" msgid="564106590567600685">"Responder"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Parar"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Fechar"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Não foi possível enviar a resposta. Tente novamente."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Não foi possível enviar a resposta. Dispositivo não conectado."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s disse"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Não é possível ler a mensagem em voz alta."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Resposta enviada para %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Nome indisponível"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ro/strings.xml b/car-messenger-common/res/values-ro/strings.xml
deleted file mode 100644
index 87fe506..0000000
--- a/car-messenger-common/res/values-ro/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="few">%d mesaje noi</item>
-      <item quantity="other">%d de mesaje noi</item>
-      <item quantity="one">Mesaj nou</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Redați"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Marcați mesajul drept citit"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Repetați"</string>
-    <string name="action_reply" msgid="564106590567600685">"Răspundeți"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Opriți"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Închideți"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nu se poate trimite răspunsul. Încercați din nou."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nu se poate trimite răspunsul. Dispozitivul nu este conectat."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s spune"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Nu se poate citi mesajul."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Răspuns trimis la %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Numele nu este disponibil"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ru/strings.xml b/car-messenger-common/res/values-ru/strings.xml
deleted file mode 100644
index bfa047d..0000000
--- a/car-messenger-common/res/values-ru/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d новое сообщение</item>
-      <item quantity="few">%d новых сообщения</item>
-      <item quantity="many">%d новых сообщений</item>
-      <item quantity="other">%d новых сообщения</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Воспроизвести"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Прочитано"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Повторить"</string>
-    <string name="action_reply" msgid="564106590567600685">"Ответить"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Остановить"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Закрыть"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Не удалось отправить ответ. Повторите попытку."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Не удалось отправить ответ. Устройство не подключено."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s говорит"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Не удалось прочитать сообщение."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Ответ отправлен пользователю %s."</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Имя недоступно"</string>
-</resources>
diff --git a/car-messenger-common/res/values-si/strings.xml b/car-messenger-common/res/values-si/strings.xml
deleted file mode 100644
index fdab619..0000000
--- a/car-messenger-common/res/values-si/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">නව පණිවුඩ %d ක්</item>
-      <item quantity="other">නව පණිවුඩ %d ක්</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ධාවනය"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"කියවූ ලෙස ලකුණු කරන්න"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"නැවත කරන්න"</string>
-    <string name="action_reply" msgid="564106590567600685">"පිළිතුරු දෙන්න"</string>
-    <string name="action_stop" msgid="6950369080845695405">"නවත්වන්න"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"වසන්න"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"පිළිතුර යැවිය නොහැක. නැවත උත්සාහ කරන්න."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"පිළිතුර යැවිය නොහැක. උපාංගය සම්බන්ධ කර නැත."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s කියන්නේ"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"පණිවිඩය කියවිය නොහැක."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"පිළිතුර %s වෙත යැවුවා"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"නම නොලැබේ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sk/strings.xml b/car-messenger-common/res/values-sk/strings.xml
deleted file mode 100644
index 7053f2c..0000000
--- a/car-messenger-common/res/values-sk/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="few">%d nové správy</item>
-      <item quantity="many">%d new messages</item>
-      <item quantity="other">%d nových správ</item>
-      <item quantity="one">Nová správa</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Prehrať"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označiť ako prečítané"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Opakovať"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odpovedať"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Zastaviť"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zavrieť"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Odpoveď sa nedá odoslať. Skúste to znova."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Odpoveď sa nedá odoslať. Zariadenie nie je pripojené."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s hovorí"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Správu sa nepodarilo prečítať."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"%s"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odpoveď bola odoslaná do systému %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Meno nie je k dispozícii"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sl/strings.xml b/car-messenger-common/res/values-sl/strings.xml
deleted file mode 100644
index 8dab1c4..0000000
--- a/car-messenger-common/res/values-sl/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d novo sporočilo</item>
-      <item quantity="two">%d novi sporočili</item>
-      <item quantity="few">%d nova sporočila</item>
-      <item quantity="other">%d novih sporočil</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Predvajaj"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Označi kot prebrano"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ponovi"</string>
-    <string name="action_reply" msgid="564106590567600685">"Odgovori"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Ustavi"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Zapri"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Odgovora ni mogoče poslati. Poskusite znova."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Odgovora ni mogoče poslati. Naprava ni povezana."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s pravi"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Sporočila ni mogoče prebrati na glas."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"»%s«"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Odgovor poslan osebi %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ime ni na voljo"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sq/strings.xml b/car-messenger-common/res/values-sq/strings.xml
deleted file mode 100644
index eb33473..0000000
--- a/car-messenger-common/res/values-sq/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d mesazhe të reja</item>
-      <item quantity="one">Mesazh i ri</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Luaj"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Shëno si të lexuar"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Përsërit"</string>
-    <string name="action_reply" msgid="564106590567600685">"Përgjigju"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Ndalo"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Mbyll"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Nuk mund të dërgohet. Provo përsëri."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Nuk mund të dërgohet. Pajisja nuk është e lidhur."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s thotë"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Mesazhi nuk mund të lexohet me zë."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Përgjigjja u dërgua te %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Emri nuk ofrohet"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sr/strings.xml b/car-messenger-common/res/values-sr/strings.xml
deleted file mode 100644
index 19601c9..0000000
--- a/car-messenger-common/res/values-sr/strings.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d нова порука</item>
-      <item quantity="few">%d нове поруке</item>
-      <item quantity="other">%d нових порука</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Пусти"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Означи као прочитано"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Понови"</string>
-    <string name="action_reply" msgid="564106590567600685">"Одговори"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Заустави"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Затвори"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Слање одговора није успело. Пробајте поново."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Слање одговора није успело. Уређај није повезан."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s каже"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Читање поруке наглас није успело."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"„%s“"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Одговор је послат контакту %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Име није доступно"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sv/strings.xml b/car-messenger-common/res/values-sv/strings.xml
deleted file mode 100644
index ef6dd39..0000000
--- a/car-messenger-common/res/values-sv/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d nya meddelanden</item>
-      <item quantity="one">Nytt meddelande</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Spela upp"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Markera som läst"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Upprepa"</string>
-    <string name="action_reply" msgid="564106590567600685">"Svara"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Stopp"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Stäng"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Det gick inte att skicka svaret. Försök igen."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Det gick inte att skicka svaret. Enheten är inte ansluten."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s säger"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Det går inte att läsa upp meddelandet."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"%s"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Svaret har skickats till %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Namnet är inte tillgängligt"</string>
-</resources>
diff --git a/car-messenger-common/res/values-sw/strings.xml b/car-messenger-common/res/values-sw/strings.xml
deleted file mode 100644
index 4edf7cd..0000000
--- a/car-messenger-common/res/values-sw/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">Ujumbe %d mpya</item>
-      <item quantity="one">Ujumbe mpya</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Cheza"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Tia Alama Kuwa Umesomwa"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Rudia"</string>
-    <string name="action_reply" msgid="564106590567600685">"Jibu"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Komesha"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Funga"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Imeshindwa kutuma jibu. Tafadhali jaribu tena."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Imeshindwa kutuma jibu. Kifaa hakijaunganishwa."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s anasema"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Imeshindwa kusoma ujumbe."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Jibu limetumwa kwa %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Jina halipatikani"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ta/strings.xml b/car-messenger-common/res/values-ta/strings.xml
deleted file mode 100644
index 2bd1e27..0000000
--- a/car-messenger-common/res/values-ta/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d புதிய மெசேஜ்கள்</item>
-      <item quantity="one">புதிய மெசேஜ்</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"பிளே செய்"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"படித்ததாகக் குறி"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"மீண்டும்"</string>
-    <string name="action_reply" msgid="564106590567600685">"பதிலளி"</string>
-    <string name="action_stop" msgid="6950369080845695405">"நிறுத்து"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"மூடுக"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"பதிலை அனுப்ப முடியவில்லை. மீண்டும் முயலவும்."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"பதிலை அனுப்ப முடியவில்லை. சாதனம் இணைக்கப்படவில்லை."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s மெசேஜ்"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"மெசேஜைப் படிக்க முடியவில்லை."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%sக்கு பதில் அனுப்பப்பட்டது"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"பெயர் கிடைக்கவில்லை"</string>
-</resources>
diff --git a/car-messenger-common/res/values-te/strings.xml b/car-messenger-common/res/values-te/strings.xml
deleted file mode 100644
index 1d62779..0000000
--- a/car-messenger-common/res/values-te/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d కొత్త సందేశాలు</item>
-      <item quantity="one">కొత్త సందేశం</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"ప్లే చేయి"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"చదివినట్లు గుర్తు పెట్టు"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"పునరావృతం చేయి"</string>
-    <string name="action_reply" msgid="564106590567600685">"ప్రత్యుత్తరమివ్వు"</string>
-    <string name="action_stop" msgid="6950369080845695405">"ఆపివేయి"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"మూసివేయి"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ప్రత్యుత్తరం పంపడం సాధ్యం కాలేదు. దయచేసి మళ్లీ ప్రయత్నించండి."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ప్రత్యుత్తరం పంపడం సాధ్యం కాలేదు. పరికరం కనెక్ట్ కాలేదు."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s చెప్పారు"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"సందేశాన్ని చదవడం సాధ్యం కాలేదు."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s కు ప్రత్యుత్తరం పంపబడింది"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"పేరు అందుబాటులో లేదు"</string>
-</resources>
diff --git a/car-messenger-common/res/values-th/strings.xml b/car-messenger-common/res/values-th/strings.xml
deleted file mode 100644
index b6cfc1e..0000000
--- a/car-messenger-common/res/values-th/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">ข้อความใหม่ %d ข้อความ</item>
-      <item quantity="one">ข้อความใหม่</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"เล่น"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"ทำเครื่องหมายว่าอ่านแล้ว"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"เล่นซ้ำ"</string>
-    <string name="action_reply" msgid="564106590567600685">"ตอบ"</string>
-    <string name="action_stop" msgid="6950369080845695405">"หยุด"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"ปิด"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"ส่งการตอบกลับไม่ได้ โปรดลองอีกครั้ง"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"ส่งการตอบกลับไม่ได้ อุปกรณ์ไม่ได้เชื่อมต่ออยู่"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s บอกว่า"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"อ่านออกเสียงข้อความไม่ได้"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"ส่งการตอบกลับถึง %s แล้ว"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"ไม่มีชื่อ"</string>
-</resources>
diff --git a/car-messenger-common/res/values-tl/strings.xml b/car-messenger-common/res/values-tl/strings.xml
deleted file mode 100644
index 0a09495..0000000
--- a/car-messenger-common/res/values-tl/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d bagong mensahe</item>
-      <item quantity="other">%d na bagong mensahe</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"I-play"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Markahan Bilang Nabasa Na"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Ulitin"</string>
-    <string name="action_reply" msgid="564106590567600685">"Sumagot"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Ihinto"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Isara"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Hindi maipadala ang sagot. Pakisubukan ulit."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Hindi maipadala ang sagot. Hindi nakakonekta ang device."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"Sabi ni %s,"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Hindi mabasa ang mensahe."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Naipadala ang sagot kay %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Hindi available ang pangalan"</string>
-</resources>
diff --git a/car-messenger-common/res/values-tr/strings.xml b/car-messenger-common/res/values-tr/strings.xml
deleted file mode 100644
index 8c8126c..0000000
--- a/car-messenger-common/res/values-tr/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d yeni mesaj</item>
-      <item quantity="one">Yeni mesaj</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Oynat"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Okundu Olarak İşaretle"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Tekrar"</string>
-    <string name="action_reply" msgid="564106590567600685">"Yanıtla"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Durdur"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Kapat"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Yanıt gönderilemedi. Lütfen tekrar deneyin."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Yanıt gönderilemedi. Cihaz bağlı değil."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s diyor ki"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Mesaj sesli okunamıyor."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"%s adlı kişiye yanıt gönderilemedi"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Ad gösterilemiyor"</string>
-</resources>
diff --git a/car-messenger-common/res/values-uk/strings.xml b/car-messenger-common/res/values-uk/strings.xml
deleted file mode 100644
index 0b46853..0000000
--- a/car-messenger-common/res/values-uk/strings.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d нове повідомлення</item>
-      <item quantity="few">%d нові повідомлення</item>
-      <item quantity="many">%d нових повідомлень</item>
-      <item quantity="other">%d нового повідомлення</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Відтворити"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Позначити як прочитане"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Повторити"</string>
-    <string name="action_reply" msgid="564106590567600685">"Відповісти"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Зупинити"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Закрити"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Неможливо надіслати відповідь. Повторіть спробу."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Неможливо надіслати відповідь. Пристрій не підключено."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"Повідомлення від користувача %s"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Не вдалося озвучити повідомлення."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Відповідь, надіслана користувачу %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Назва недоступна"</string>
-</resources>
diff --git a/car-messenger-common/res/values-ur/strings.xml b/car-messenger-common/res/values-ur/strings.xml
deleted file mode 100644
index 542b43c..0000000
--- a/car-messenger-common/res/values-ur/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">‎%d نئے پیغامات</item>
-      <item quantity="one">نیا پیغام</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"چلائیں"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"پڑھا ہوا کے بطور نشان زد کریں"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"مکرر"</string>
-    <string name="action_reply" msgid="564106590567600685">"جواب دیں"</string>
-    <string name="action_stop" msgid="6950369080845695405">"روکیں"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"بند کریں"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"جواب بھیجنے سے قاصر۔ براہ کرم دوبارہ کوشش کریں۔"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"جواب بھیجنے سے قاصر۔ آلہ منسلک نہیں ہے۔"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s کا کہنا ہے"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"پیغام نہیں پڑھا جا سکتا۔"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"جواب %s پر بھیجا گیا"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"نام دستیاب نہیں ہے"</string>
-</resources>
diff --git a/car-messenger-common/res/values-uz/strings.xml b/car-messenger-common/res/values-uz/strings.xml
deleted file mode 100644
index d886171..0000000
--- a/car-messenger-common/res/values-uz/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d ta yangi xabar</item>
-      <item quantity="one">Yangi xabar</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Ijro"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Ochilgan deb belgilash"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Takrorlash"</string>
-    <string name="action_reply" msgid="564106590567600685">"Javob berish"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Toʻxtatish"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Yopish"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Javob yuborilmadi. Qayta urining."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Javob yuborilmadi. Qurilma ulanmagan."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s dedi"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Xabar oʻqilmadi."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"“%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Javob bunga yuborildi: %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Bu nom mavjud emas"</string>
-</resources>
diff --git a/car-messenger-common/res/values-vi/strings.xml b/car-messenger-common/res/values-vi/strings.xml
deleted file mode 100644
index bc23c3e..0000000
--- a/car-messenger-common/res/values-vi/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d tin nhắn mới</item>
-      <item quantity="one">Tin nhắn mới</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Phát"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Đánh dấu là đã đọc"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Lặp lại"</string>
-    <string name="action_reply" msgid="564106590567600685">"Trả lời"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Dừng"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Đóng"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Không gửi được nội dung trả lời. Vui lòng thử lại."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Không gửi được nội dung trả lời. Thiết bị chưa được kết nối."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s nhắn"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Không thể đọc to thông báo."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Đã gửi nội dung trả lời tới %s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Không có tên này"</string>
-</resources>
diff --git a/car-messenger-common/res/values-zh-rCN/strings.xml b/car-messenger-common/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 97088d9..0000000
--- a/car-messenger-common/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d 条新消息</item>
-      <item quantity="one">1 条新消息</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"播放"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"标记为已读"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"重复"</string>
-    <string name="action_reply" msgid="564106590567600685">"回复"</string>
-    <string name="action_stop" msgid="6950369080845695405">"停止"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"关闭"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"无法发送回复。请重试。"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"无法发送回复。设备未连接。"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s说"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"无法读出消息。"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"“%s”"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"已将回复发送给%s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"名称不可用"</string>
-</resources>
diff --git a/car-messenger-common/res/values-zh-rHK/strings.xml b/car-messenger-common/res/values-zh-rHK/strings.xml
deleted file mode 100644
index bdc6a72..0000000
--- a/car-messenger-common/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d 個新訊息</item>
-      <item quantity="one">新訊息</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"播放"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"標示為已讀"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"重複"</string>
-    <string name="action_reply" msgid="564106590567600685">"回覆"</string>
-    <string name="action_stop" msgid="6950369080845695405">"停止"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"關閉"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"無法傳送回覆,請再試一次。"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"無法傳送回覆,裝置未連接。"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"%s話"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"無法讀出訊息。"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"「%s」"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"已向%s傳送回覆"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"找不到名稱"</string>
-</resources>
diff --git a/car-messenger-common/res/values-zh-rTW/strings.xml b/car-messenger-common/res/values-zh-rTW/strings.xml
deleted file mode 100644
index a94a58b..0000000
--- a/car-messenger-common/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="other">%d 則新訊息</item>
-      <item quantity="one">新訊息</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"播放"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"標示為已讀取"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"重複播放"</string>
-    <string name="action_reply" msgid="564106590567600685">"回覆"</string>
-    <string name="action_stop" msgid="6950369080845695405">"停止"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"關閉"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"無法傳送回覆,請再試一次。"</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"尚未與裝置連線,因此無法傳送回覆。"</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"「%s」說:"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"無法朗讀訊息。"</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"「%s」"</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"已將回覆傳送給「%s」"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"無法使用這個名稱"</string>
-</resources>
diff --git a/car-messenger-common/res/values-zu/strings.xml b/car-messenger-common/res/values-zu/strings.xml
deleted file mode 100644
index 1292512..0000000
--- a/car-messenger-common/res/values-zu/strings.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?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"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <plurals name="notification_new_message" formatted="false" msgid="1631343923556571689">
-      <item quantity="one">%d imilayezo emisha</item>
-      <item quantity="other">%d imilayezo emisha</item>
-    </plurals>
-    <string name="action_play" msgid="1884580550634079470">"Dlala"</string>
-    <string name="action_mark_as_read" msgid="5185216939940407938">"Maka njengokufundiwe"</string>
-    <string name="action_repeat" msgid="8184323082093728957">"Phinda"</string>
-    <string name="action_reply" msgid="564106590567600685">"Phendula"</string>
-    <string name="action_stop" msgid="6950369080845695405">"Misa"</string>
-    <string name="action_close_messages" msgid="7949295965012770696">"Vala"</string>
-    <string name="auto_reply_failed_message" msgid="6445984971657465627">"Ayikwazi ukuthumela impendulo. Sicela uzame futhi."</string>
-    <string name="auto_reply_device_disconnected" msgid="5861772755278229950">"Ayikwazi ukuthumela impendulo. Idivayisi ayixhunyiwe."</string>
-    <string name="tts_sender_says" msgid="5352698006545359668">"U-%s uthi"</string>
-    <string name="tts_failed_toast" msgid="1483313550894086353">"Ayikwazi ukufundela phezulu umlayezo."</string>
-    <string name="reply_message_display_template" msgid="6348622926232346974">"\"%s\""</string>
-    <string name="message_sent_notice" msgid="7172592196465284673">"Impendulo ithunyelwe ku-%s"</string>
-    <string name="name_not_available" msgid="3800013092212550915">"Igama alitholakali"</string>
-</resources>
diff --git a/car-messenger-common/res/values/strings.xml b/car-messenger-common/res/values/strings.xml
deleted file mode 100644
index b19ffdb..0000000
--- a/car-messenger-common/res/values/strings.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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>
-    <plurals name="notification_new_message">
-        <item quantity="one">New message</item>
-        <item quantity="other">%d new messages</item>
-    </plurals>
-
-    <string name="action_play">Play</string>
-    <string name="action_mark_as_read">Mark As Read</string>
-    <string name="action_repeat">Repeat</string>
-    <string name="action_reply">Reply</string>
-    <string name="action_stop">Stop</string>
-    <string name="action_close_messages">Close</string>
-    <string name="auto_reply_failed_message">Unable to send reply. Please try again.</string>
-    <string name="auto_reply_device_disconnected">Unable to send reply. Device is not connected.
-    </string>
-
-    <string name="tts_sender_says">%s says</string>
-
-    <string name="tts_failed_toast">Can\'t read out message.</string>
-    <string name="reply_message_display_template">\"%s\"</string>
-    <string name="message_sent_notice">Reply sent to %s</string>
-
-    <!-- Default Sender name that appears in message notification if sender name is not available. [CHAR_LIMIT=NONE] -->
-    <string name="name_not_available">Name not available</string>
-
-    <!-- Formats a group conversation's title for a message notification. The format is: <Sender of last message> mdot <Name of the conversation>.-->
-    <string name="group_conversation_title_separator" translatable="false">&#160;&#8226;&#160;</string>
-
-</resources>
diff --git a/car-messenger-common/src/com/android/car/messenger/common/BaseNotificationDelegate.java b/car-messenger-common/src/com/android/car/messenger/common/BaseNotificationDelegate.java
deleted file mode 100644
index 89bf029..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/BaseNotificationDelegate.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * 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.messenger.common;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationCompat.Action;
-import androidx.core.app.Person;
-import androidx.core.graphics.drawable.IconCompat;
-
-import com.android.car.telephony.common.TelecomUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Predicate;
-
-/**
- * Base Interface for Message Notification Delegates.
- * Any Delegate who chooses to extend from this class is responsible for:
- * <p> device connection logic </p>
- * <p> sending and receiving messages from the connected devices </p>
- * <p> creation of {@link ConversationNotificationInfo} and {@link Message} objects </p>
- * <p> creation of {@link ConversationKey}, {@link MessageKey}, {@link SenderKey} </p>
- * <p> loading of largeIcons for each Sender per device </p>
- * <p> Mark-as-Read and Reply functionality  </p>
- **/
-public class BaseNotificationDelegate {
-
-    /** Used to reply to message. */
-    public static final String ACTION_REPLY = "com.android.car.messenger.common.ACTION_REPLY";
-
-    /** Used to clear notification state when user dismisses notification. */
-    public static final String ACTION_DISMISS_NOTIFICATION =
-            "com.android.car.messenger.common.ACTION_DISMISS_NOTIFICATION";
-
-    /** Used to mark a notification as read **/
-    public static final String ACTION_MARK_AS_READ =
-            "com.android.car.messenger.common.ACTION_MARK_AS_READ";
-
-    /* EXTRAS */
-    /** Key under which the {@link ConversationKey} is provided. */
-    public static final String EXTRA_CONVERSATION_KEY =
-            "com.android.car.messenger.common.EXTRA_CONVERSATION_KEY";
-
-    /**
-     * The resultKey of the {@link RemoteInput} which is sent in the reply callback {@link
-     * Notification.Action}.
-     */
-    public static final String EXTRA_REMOTE_INPUT_KEY =
-            "com.android.car.messenger.common.REMOTE_INPUT_KEY";
-
-    protected final Context mContext;
-    protected NotificationManager mNotificationManager;
-    protected final boolean mUseLetterTile;
-
-    /**
-     * Maps a conversation's Notification Metadata to the conversation's unique key.
-     * The extending class should always keep this map updated with the latest new/updated
-     * notification information before calling {@link BaseNotificationDelegate#postNotification(
-     * ConversationKey, ConversationNotificationInfo, String)}.
-     **/
-    protected final Map<ConversationKey, ConversationNotificationInfo> mNotificationInfos =
-            new HashMap<>();
-
-    /**
-     * Maps a conversation's Notification Builder to the conversation's unique key. When the
-     * conversation gets updated, this builder should be retrieved, updated, and reposted.
-     **/
-    private final Map<ConversationKey, NotificationCompat.Builder> mNotificationBuilders =
-            new HashMap<>();
-
-    /**
-     * Maps a message's metadata with the message's unique key.
-     * The extending class should always keep this map updated with the latest message information
-     * before calling {@link BaseNotificationDelegate#postNotification(
-     * ConversationKey, ConversationNotificationInfo, String)}.
-     **/
-    protected final Map<MessageKey, Message> mMessages = new HashMap<>();
-
-    private final int mBitmapSize;
-    private final float mCornerRadiusPercent;
-
-    /**
-     * Constructor for the BaseNotificationDelegate class.
-     * @param context of the calling application.
-     * @param useLetterTile whether a letterTile icon should be used if no avatar icon is given.
-     **/
-    public BaseNotificationDelegate(Context context, boolean useLetterTile) {
-        mContext = context;
-        mUseLetterTile = useLetterTile;
-        mNotificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        mBitmapSize =
-                mContext.getResources()
-                        .getDimensionPixelSize(R.dimen.notification_contact_photo_size);
-        mCornerRadiusPercent = mContext.getResources()
-                .getFloat(R.dimen.contact_avatar_corner_radius_percent);
-    }
-
-    /**
-     * Removes all messages related to the inputted predicate, and cancels their notifications.
-     **/
-    public void cleanupMessagesAndNotifications(Predicate<CompositeKey> predicate) {
-        clearNotifications(predicate);
-        mNotificationBuilders.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
-        mNotificationInfos.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
-        mMessages.entrySet().removeIf(
-                messageKeyMapMessageEntry -> predicate.test(messageKeyMapMessageEntry.getKey()));
-    }
-
-    /**
-     * Clears all notifications matching the predicate. Example method calls are when user
-     * wants to clear (a) message notification(s), or when the Bluetooth device that received the
-     * messages has been disconnected.
-     */
-    public void clearNotifications(Predicate<CompositeKey> predicate) {
-        mNotificationInfos.forEach((conversationKey, notificationInfo) -> {
-            if (predicate.test(conversationKey)) {
-                mNotificationManager.cancel(notificationInfo.getNotificationId());
-            }
-        });
-    }
-
-    protected void dismissInternal(ConversationKey convoKey) {
-        clearNotifications(key -> key.equals(convoKey));
-        excludeFromNotification(convoKey);
-    }
-
-    /**
-     * Excludes messages from a notification so that the messages are not shown to the user once
-     * the notification gets updated with newer messages.
-     */
-    protected void excludeFromNotification(ConversationKey convoKey) {
-        ConversationNotificationInfo info = mNotificationInfos.get(convoKey);
-        for (MessageKey key : info.mMessageKeys) {
-            Message message = mMessages.get(key);
-            message.excludeFromNotification();
-        }
-    }
-
-    /**
-     * Helper method to add {@link Message}s to the {@link ConversationNotificationInfo}. This
-     * should be called when a new message has arrived.
-     **/
-    protected void addMessageToNotificationInfo(Message message, ConversationKey convoKey) {
-        MessageKey messageKey = new MessageKey(message);
-        boolean repeatMessage = mMessages.containsKey(messageKey);
-        mMessages.put(messageKey, message);
-        if (!repeatMessage) {
-            ConversationNotificationInfo notificationInfo = mNotificationInfos.get(convoKey);
-            notificationInfo.mMessageKeys.add(messageKey);
-        }
-    }
-
-    /**
-     * Creates a new notification, or updates an existing notification with the latest messages,
-     * then posts it.
-     * This should be called after the {@link ConversationNotificationInfo} object has been created,
-     * and all of its {@link Message} objects have been linked to it.
-     **/
-    protected void postNotification(ConversationKey conversationKey,
-            ConversationNotificationInfo notificationInfo, String channelId,
-            @Nullable Bitmap avatarIcon) {
-        boolean newNotification = !mNotificationBuilders.containsKey(conversationKey);
-
-        NotificationCompat.Builder builder = newNotification ? new NotificationCompat.Builder(
-                mContext, channelId) : mNotificationBuilders.get(
-                conversationKey);
-        builder.setChannelId(channelId);
-        Message lastMessage = mMessages.get(notificationInfo.mMessageKeys.getLast());
-
-        builder.setContentTitle(notificationInfo.getConvoTitle());
-        builder.setContentText(mContext.getResources().getQuantityString(
-                R.plurals.notification_new_message, notificationInfo.mMessageKeys.size(),
-                notificationInfo.mMessageKeys.size()));
-
-        if (avatarIcon != null) {
-            builder.setLargeIcon(avatarIcon);
-        } else if (mUseLetterTile) {
-            BitmapDrawable drawable = (BitmapDrawable) TelecomUtils.createLetterTile(mContext,
-                    Utils.getInitials(lastMessage.getSenderName(), ""),
-                    lastMessage.getSenderName(), mBitmapSize, mCornerRadiusPercent)
-                    .loadDrawable(mContext);
-            builder.setLargeIcon(drawable.getBitmap());
-        }
-        // Else, no avatar icon will be shown.
-
-        builder.setWhen(lastMessage.getReceivedTime());
-
-        // Create MessagingStyle
-        String userName = (notificationInfo.getUserDisplayName() == null
-                || notificationInfo.getUserDisplayName().isEmpty()) ? mContext.getString(
-                R.string.name_not_available) : notificationInfo.getUserDisplayName();
-        Person user = new Person.Builder()
-                .setName(userName)
-                .build();
-        NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(
-                user);
-        Person sender = new Person.Builder()
-                .setName(lastMessage.getSenderName())
-                .setUri(lastMessage.getSenderContactUri())
-                .build();
-        notificationInfo.mMessageKeys.stream().map(mMessages::get).forEachOrdered(message -> {
-            if (!message.shouldExcludeFromNotification()) {
-                messagingStyle.addMessage(
-                        message.getMessageText(),
-                        message.getReceivedTime(),
-                        notificationInfo.isGroupConvo() ? new Person.Builder()
-                                .setName(message.getSenderName())
-                                .setUri(message.getSenderContactUri())
-                                .build() : sender);
-            }
-        });
-        if (notificationInfo.isGroupConvo()) {
-            messagingStyle.setConversationTitle(Utils.constructGroupConversationHeader(
-                    lastMessage.getSenderName(), notificationInfo.getConvoTitle(),
-                    mContext.getString(R.string.group_conversation_title_separator)
-            ));
-        }
-
-        // We are creating this notification for the first time.
-        if (newNotification) {
-            builder.setCategory(Notification.CATEGORY_MESSAGE);
-            if (notificationInfo.getAppIcon() != null) {
-                builder.setSmallIcon(IconCompat.createFromIcon(notificationInfo.getAppIcon()));
-            } else {
-                builder.setSmallIcon(R.drawable.ic_message);
-            }
-
-            builder.setShowWhen(true);
-            messagingStyle.setGroupConversation(notificationInfo.isGroupConvo());
-
-            if (notificationInfo.getAppDisplayName() != null) {
-                Bundle displayName = new Bundle();
-                displayName.putCharSequence(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                        notificationInfo.getAppDisplayName());
-                builder.addExtras(displayName);
-            }
-
-            PendingIntent deleteIntent = createServiceIntent(conversationKey,
-                    notificationInfo.getNotificationId(),
-                    ACTION_DISMISS_NOTIFICATION, /* isMutable= */ false);
-            builder.setDeleteIntent(deleteIntent);
-
-            List<Action> actions = buildNotificationActions(conversationKey,
-                    notificationInfo.getNotificationId());
-            for (final Action action : actions) {
-                builder.addAction(action);
-            }
-        }
-        builder.setStyle(messagingStyle);
-
-        mNotificationBuilders.put(conversationKey, builder);
-        mNotificationManager.notify(notificationInfo.getNotificationId(), builder.build());
-    }
-
-    /** Can be overridden by any Delegates that have some devices that do not support reply. **/
-    protected boolean shouldAddReplyAction(String deviceAddress) {
-        return true;
-    }
-
-    private List<Action> buildNotificationActions(ConversationKey conversationKey,
-            int notificationId) {
-        final int icon = android.R.drawable.ic_media_play;
-
-        final List<NotificationCompat.Action> actionList = new ArrayList<>();
-
-        // Reply action
-        if (shouldAddReplyAction(conversationKey.getDeviceId())) {
-            final String replyString = mContext.getString(R.string.action_reply);
-            PendingIntent replyIntent = createServiceIntent(conversationKey, notificationId,
-                    ACTION_REPLY, /* isMutable= */ true);
-            actionList.add(
-                    new NotificationCompat.Action.Builder(icon, replyString, replyIntent)
-                            .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
-                            .setShowsUserInterface(false)
-                            .addRemoteInput(
-                                    new androidx.core.app.RemoteInput.Builder(
-                                            EXTRA_REMOTE_INPUT_KEY)
-                                            .build()
-                            )
-                            .build()
-            );
-        }
-
-        // Mark-as-read Action. This will be the callback of Notification Center's "Read" action.
-        final String markAsRead = mContext.getString(R.string.action_mark_as_read);
-        PendingIntent markAsReadIntent = createServiceIntent(conversationKey, notificationId,
-                ACTION_MARK_AS_READ, /* isMutable= */ false);
-        actionList.add(
-                new NotificationCompat.Action.Builder(icon, markAsRead, markAsReadIntent)
-                        .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
-                        .setShowsUserInterface(false)
-                        .build()
-        );
-
-        return actionList;
-    }
-
-    private PendingIntent createServiceIntent(ConversationKey conversationKey, int notificationId,
-            String action, boolean isMutable) {
-        Intent intent = new Intent(mContext, mContext.getClass())
-                .setAction(action)
-                .setClassName(mContext, mContext.getClass().getName())
-                .putExtra(EXTRA_CONVERSATION_KEY, conversationKey);
-
-        int flags = PendingIntent.FLAG_UPDATE_CURRENT;
-        flags |= isMutable ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE;
-
-        return PendingIntent.getForegroundService(mContext, notificationId, intent, flags);
-    }
-
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/CompositeKey.java b/car-messenger-common/src/com/android/car/messenger/common/CompositeKey.java
deleted file mode 100644
index 4d8bacd..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/CompositeKey.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.car.messenger.common;
-
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * A composite key used for {@link Map} lookups, using two strings for
- * checking equality and hashing.
- */
-public abstract class CompositeKey {
-    private final String mDeviceId;
-    private final String mSubKey;
-
-    protected CompositeKey(String deviceId, String subKey) {
-        mDeviceId = deviceId;
-        mSubKey = subKey;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof CompositeKey)) {
-            return false;
-        }
-
-        CompositeKey that = (CompositeKey) o;
-        return Objects.equals(mDeviceId, that.mDeviceId)
-                && Objects.equals(mSubKey, that.mSubKey);
-    }
-
-    /**
-     * Returns true if the device address of this composite key equals {@code deviceId}.
-     *
-     * @param deviceId the device address which is compared to this key's device address
-     * @return true if the device addresses match
-     */
-    public boolean matches(String deviceId) {
-        return mDeviceId.equals(deviceId);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mDeviceId, mSubKey);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s, deviceId: %s, subKey: %s",
-                getClass().getSimpleName(), mDeviceId, mSubKey);
-    }
-
-    /** Returns this composite key's device address. */
-    public String getDeviceId() {
-        return mDeviceId;
-    }
-
-    /** Returns this composite key's sub key. */
-    public String getSubKey() {
-        return mSubKey;
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/ConversationKey.java b/car-messenger-common/src/com/android/car/messenger/common/ConversationKey.java
deleted file mode 100644
index cf7a737..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/ConversationKey.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.messenger.common;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * {@link CompositeKey} subclass used to give each conversation on all the connected devices a
- * unique Key.
- */
-public class ConversationKey extends CompositeKey implements Parcelable {
-
-    public ConversationKey(String deviceId, String key) {
-        super(deviceId, key);
-    }
-
-    /** Creates a ConversationKey from a BluetoothMapClient intent. **/
-    public static ConversationKey createConversationKey(Intent intent) {
-        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        String senderUri = Utils.getSenderUri(intent);
-        String senderName = Utils.getSenderName(intent);
-        String subKey = senderName + "/" + senderUri;
-        if (Utils.isGroupConversation(intent)) {
-            List<String> names = Utils.getInclusiveRecipientsUrisList(intent);
-            Collections.sort(names);
-            subKey = names.toString();
-        }
-        return new ConversationKey(device.getAddress(), subKey);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(getDeviceId());
-        dest.writeString(getSubKey());
-    }
-
-    /** Creates {@link ConversationKey} instances from {@link Parcel} sources. */
-    public static final Parcelable.Creator<ConversationKey> CREATOR =
-            new Parcelable.Creator<ConversationKey>() {
-                @Override
-                public ConversationKey createFromParcel(Parcel source) {
-                    return new ConversationKey(source.readString(), source.readString());
-                }
-
-                @Override
-                public ConversationKey[] newArray(int size) {
-                    return new ConversationKey[size];
-                }
-            };
-
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/ConversationNotificationInfo.java b/car-messenger-common/src/com/android/car/messenger/common/ConversationNotificationInfo.java
deleted file mode 100644
index d563d12..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/ConversationNotificationInfo.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * 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.messenger.common;
-
-import static com.android.car.apps.common.util.SafeLog.logw;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.ConversationNotification;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyle;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.PhoneToCarMessage;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Represents a conversation notification's metadata that is shared between the conversation's
- * messages. Note, each {@link ConversationKey} should map to exactly one
- * ConversationNotificationInfo object.
- **/
-public class ConversationNotificationInfo {
-    private static final String TAG = "CMC.ConvoNotifInfo";
-    private static int sNextNotificationId = 0;
-    final int mNotificationId = sNextNotificationId++;
-
-    private final String mDeviceName;
-    private final String mDeviceId;
-    // This is always the sender name for SMS Messages from Bluetooth MAP.
-    private String mConvoTitle;
-    private final boolean mIsGroupConvo;
-
-    /** Only used for {@link NotificationMsg} conversations. **/
-    @Nullable
-    private final String mNotificationKey;
-    @Nullable
-    private final String mAppDisplayName;
-    private final String mAppPackageName;
-    @Nullable
-    private final String mUserDisplayName;
-    @Nullable
-    private final Icon mAppIcon;
-    /** Uris of all members in a MMS Group Conversation. **/
-    @Nullable
-    private final List<String> mCcRecipientsUris;
-
-    public final LinkedList<MessageKey> mMessageKeys = new LinkedList<>();
-
-    /**
-     * Creates a ConversationNotificationInfo for a {@link NotificationMsg}. Returns {@code null} if
-     * the {@link ConversationNotification} is missing required fields.
-     **/
-    @Nullable
-    public static ConversationNotificationInfo createConversationNotificationInfo(
-            @NonNull String deviceName, @NonNull String deviceId,
-            @NonNull ConversationNotification conversation, @NonNull String notificationKey) {
-        MessagingStyle messagingStyle = conversation.getMessagingStyle();
-
-        if (!Utils.isValidConversationNotification(conversation, /* isShallowCheck= */ true)) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                throw new IllegalArgumentException(
-                        "ConversationNotificationInfo is missing required fields");
-            } else {
-                logw(TAG, "ConversationNotificationInfo is missing required fields");
-                return null;
-            }
-        }
-
-        Icon appIcon = null;
-        if (conversation.getAppIcon() != null) {
-            byte[] iconBytes = conversation.getAppIcon().toByteArray();
-            appIcon = Icon.createWithData(iconBytes, 0, iconBytes.length);
-        }
-
-        return new ConversationNotificationInfo(deviceName, deviceId,
-                messagingStyle.getConvoTitle(),
-                messagingStyle.getIsGroupConvo(), notificationKey,
-                conversation.getMessagingAppDisplayName(),
-                conversation.getMessagingAppPackageName(),
-                messagingStyle.getUserDisplayName(),
-                appIcon,
-                /* ccUris= */null);
-
-    }
-    /** Creates a ConversationNotificationInfo for a BluetoothMapClient intent. **/
-    public static ConversationNotificationInfo createConversationNotificationInfo(Intent intent,
-            String conversationTitle, String appPackageName, @Nullable Icon appIcon) {
-        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-        return new ConversationNotificationInfo(device.getName(), device.getAddress(),
-                conversationTitle, Utils.isGroupConversation(intent), /* notificationKey */ null,
-                /* appDisplayName */ null, appPackageName, /* userDisplayName */ null,
-                appIcon,
-                Utils.getInclusiveRecipientsUrisList(intent));
-    }
-
-    private ConversationNotificationInfo(@Nullable String deviceName, String deviceId,
-            String convoTitle, boolean isGroupConvo, @Nullable String notificationKey,
-            @Nullable String appDisplayName, String appPackageName,
-            @Nullable String userDisplayName, @Nullable Icon appIcon,
-            @Nullable List<String> ccUris) {
-        boolean missingDeviceId = (deviceId == null);
-        boolean missingTitle = (convoTitle == null);
-        if (missingDeviceId || missingTitle) {
-            StringBuilder builder = new StringBuilder("Missing required fields:");
-            if (missingDeviceId) {
-                builder.append(" deviceId");
-            }
-            if (missingTitle) {
-                builder.append(" convoTitle");
-            }
-            throw new IllegalArgumentException(builder.toString());
-        }
-        this.mDeviceName = deviceName;
-        this.mDeviceId = deviceId;
-        this.mConvoTitle = convoTitle;
-        this.mIsGroupConvo = isGroupConvo;
-        this.mNotificationKey = notificationKey;
-        this.mAppDisplayName = appDisplayName;
-        this.mAppPackageName = appPackageName;
-        this.mUserDisplayName = userDisplayName;
-        this.mAppIcon = appIcon;
-        this.mCcRecipientsUris = ccUris;
-    }
-
-    /** Returns the id that should be used for this object's {@link android.app.Notification} **/
-    public int getNotificationId() {
-        return mNotificationId;
-    }
-
-    /** Returns the friendly name of the device that received the notification. **/
-    public String getDeviceName() {
-        return mDeviceName;
-    }
-
-    /** Returns the address of the device that received the notification. **/
-    public String getDeviceId() {
-        return mDeviceId;
-    }
-
-    /**
-     * Returns the conversation title of this notification. If this notification came from MAP
-     * profile, the title will be the Sender's name.
-     */
-    public String getConvoTitle() {
-        return mConvoTitle;
-    }
-
-    /** Update the conversation title. **/
-    public void setConvoTitle(String newTitle) {
-        mConvoTitle = newTitle;
-    }
-
-    /** Returns {@code true} if this message is in a group conversation **/
-    public boolean isGroupConvo() {
-        return mIsGroupConvo;
-    }
-
-    /**
-     * Returns the key if this conversation is based on a {@link ConversationNotification}. Refer to
-     * {@link PhoneToCarMessage#getNotificationKey()} for more info.
-     */
-    @Nullable
-    public String getNotificationKey() {
-        return mNotificationKey;
-    }
-
-    /**
-     * Returns the display name of the application that posted this notification if this object is
-     * based on a {@link ConversationNotification}.
-     **/
-    @Nullable
-    public String getAppDisplayName() {
-        return mAppDisplayName;
-    }
-
-    /**
-     * Returns the package name of the application that posted this notification.
-     **/
-    public String getAppPackageName() {
-        return mAppPackageName;
-    }
-
-    /**
-     * Returns the User Display Name if this object is based on a @link ConversationNotification}.
-     * This is needed for {@link android.app.Notification.MessagingStyle}.
-     */
-    @Nullable
-    public String getUserDisplayName() {
-        return mUserDisplayName;
-    }
-
-
-    /** Returns the app's icon of the application that posted this notification. **/
-    @Nullable
-    public Icon getAppIcon() {
-        return mAppIcon;
-    }
-
-    public MessageKey getLastMessageKey() {
-        return mMessageKeys.getLast();
-    }
-
-    /**
-     * Returns the sorted URIs of all the participants of a MMS/SMS/RCS conversation. Returns
-     * {@code null} if this is based on a {@link NotificationMsg} conversation.
-     */
-    @Nullable
-    public List<String> getCcRecipientsUris() {
-        return mCcRecipientsUris;
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/Message.java b/car-messenger-common/src/com/android/car/messenger/common/Message.java
deleted file mode 100644
index 8e016dd..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/Message.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * 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.messenger.common;
-
-import static com.android.car.apps.common.util.SafeLog.logw;
-import static com.android.car.messenger.common.Utils.BMC_EXTRA_MESSAGE_HANDLE;
-import static com.android.car.messenger.common.Utils.BMC_EXTRA_MESSAGE_READ_STATUS;
-import static com.android.car.messenger.common.Utils.BMC_EXTRA_MESSAGE_TIMESTAMP;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyleMessage;
-
-
-/**
- * Represents a SMS, MMS, and {@link NotificationMsg}. This object is based
- * on {@link NotificationMsg}.
- */
-public class Message {
-    private static final String TAG = "CMC.Message";
-
-    private final String mSenderName;
-    private final String mDeviceId;
-    private final String mMessageText;
-    private final long mReceivedTime;
-    private final boolean mIsReadOnPhone;
-    private boolean mShouldExclude;
-    private final String mHandle;
-    private final MessageType mMessageType;
-    private final SenderKey mSenderKey;
-
-
-    /**
-     * Note: MAP messages from iOS version 12 and earlier, as well as {@link MessagingStyleMessage},
-     * don't provide these.
-     */
-    @Nullable
-    final String mSenderContactUri;
-
-    /**
-     * Describes if the message was received through Bluetooth MAP or is a {@link NotificationMsg}.
-     */
-    public enum MessageType {
-        BLUETOOTH_MAP_MESSAGE, NOTIFICATION_MESSAGE
-    }
-
-    /**
-     * Creates a Message based on {@link MessagingStyleMessage}. Returns {@code null} if the {@link
-     * MessagingStyleMessage} is missing required fields.
-     *
-     * @param deviceId of the phone that received this message.
-     * @param updatedMessage containing the information to base this message object off of.
-     * @param senderKey of the sender of the message. Not guaranteed to be unique for all senders
-     *                  if this message is part of a group conversation.
-     **/
-    @Nullable
-    public static Message parseFromMessage(String deviceId,
-            MessagingStyleMessage updatedMessage, SenderKey senderKey) {
-
-        if (!Utils.isValidMessagingStyleMessage(updatedMessage)) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                throw new IllegalArgumentException(
-                        "MessagingStyleMessage is missing required fields");
-            } else {
-                logw(TAG, "MessagingStyleMessage is missing required fields");
-                return null;
-            }
-        }
-
-        return new Message(updatedMessage.getSender().getName(),
-                deviceId,
-                updatedMessage.getTextMessage(),
-                updatedMessage.getTimestamp(),
-                updatedMessage.getIsRead(),
-                Utils.createMessageHandle(updatedMessage),
-                MessageType.NOTIFICATION_MESSAGE,
-                /* senderContactUri */ null,
-                senderKey);
-    }
-
-    /**
-     * Creates a Message based on BluetoothMapClient intent. Returns {@code null} if the
-     * intent is missing required fields.
-     **/
-    public static Message parseFromIntent(Intent intent) {
-        if (!Utils.isValidMapClientIntent(intent)) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                throw new IllegalArgumentException(
-                        "BluetoothMapClient intent is missing required fields");
-            } else {
-                logw(TAG, "BluetoothMapClient intent is missing required fields");
-                return null;
-            }
-        }
-        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        String senderUri = Utils.getSenderUri(intent);
-
-        return new Message(
-                Utils.getSenderName(intent),
-                device.getAddress(),
-                intent.getStringExtra(android.content.Intent.EXTRA_TEXT),
-                intent.getLongExtra(BMC_EXTRA_MESSAGE_TIMESTAMP,
-                        System.currentTimeMillis()),
-                intent.getBooleanExtra(BMC_EXTRA_MESSAGE_READ_STATUS,
-                        false),
-                intent.getStringExtra(BMC_EXTRA_MESSAGE_HANDLE),
-                MessageType.BLUETOOTH_MAP_MESSAGE,
-                senderUri,
-                SenderKey.createSenderKey(intent)
-        );
-    }
-
-    private Message(String senderName, String deviceId, String messageText, long receivedTime,
-            boolean isReadOnPhone, String handle, MessageType messageType,
-            @Nullable String senderContactUri, SenderKey senderKey) {
-        boolean missingSenderName = (senderName == null);
-        boolean missingDeviceId = (deviceId == null);
-        boolean missingText = (messageText == null);
-        boolean missingHandle = (handle == null);
-        boolean missingType = (messageType == null);
-        if (missingSenderName || missingDeviceId || missingText || missingHandle || missingType) {
-            StringBuilder builder = new StringBuilder("Missing required fields:");
-            if (missingSenderName) {
-                builder.append(" senderName");
-            }
-            if (missingDeviceId) {
-                builder.append(" deviceId");
-            }
-            if (missingText) {
-                builder.append(" messageText");
-            }
-            if (missingHandle) {
-                builder.append(" handle");
-            }
-            if (missingType) {
-                builder.append(" type");
-            }
-            throw new IllegalArgumentException(builder.toString());
-        }
-        this.mSenderName = senderName;
-        this.mDeviceId = deviceId;
-        this.mMessageText = messageText;
-        this.mReceivedTime = receivedTime;
-        this.mIsReadOnPhone = isReadOnPhone;
-        this.mShouldExclude = false;
-        this.mHandle = handle;
-        this.mMessageType = messageType;
-        this.mSenderContactUri = senderContactUri;
-        this.mSenderKey = senderKey;
-    }
-
-    /**
-     * Returns the contact name as obtained from the device.
-     * If contact is in the device's address-book, this is typically the contact name.
-     * Otherwise it will be the phone number.
-     */
-    public String getSenderName() {
-        return mSenderName;
-    }
-
-    /**
-     * Returns the id of the device from which this message was received.
-     */
-    public String getDeviceId() {
-        return mDeviceId;
-    }
-
-    /**
-     * Returns the actual content of the message.
-     */
-    public String getMessageText() {
-        return mMessageText;
-    }
-
-    /**
-     * Returns the milliseconds since epoch at which this message notification was received on the
-     * head-unit.
-     */
-    public long getReceivedTime() {
-        return mReceivedTime;
-    }
-
-    /**
-     * Whether message should be included in the notification. Messages that have been read aloud on
-     * the car, or that have been dismissed by the user should be excluded from the notification if/
-     * when the notification gets updated. Note: this state will not be propagated to the phone.
-     */
-    public void excludeFromNotification() {
-        mShouldExclude = true;
-    }
-
-    /**
-     * Returns {@code true} if message was read on the phone before it was received on the car.
-     */
-    public boolean isReadOnPhone() {
-        return mIsReadOnPhone;
-    }
-
-    /**
-     * Returns {@code true} if message should not be included in the notification. Messages that
-     * have been read aloud on the car, or that have been dismissed by the user should be excluded
-     * from the notification if/when the notification gets updated.
-     */
-    public boolean shouldExcludeFromNotification() {
-        return mShouldExclude;
-    }
-
-    /**
-     * Returns a unique handle/key for this message. This is used as this Message's
-     * {@link MessageKey#getSubKey()} Note: this handle might only be unique for the lifetime of a
-     * device connection session.
-     */
-    public String getHandle() {
-        return mHandle;
-    }
-
-    /**
-     * If the message came from BluetoothMapClient, this retrieves a key that is unique
-     * for each contact per device.
-     * If the message came from {@link NotificationMsg}, this retrieves a key that is only
-     * guaranteed to be unique per sender in a 1-1 conversation. If this message is part of a
-     * group conversation, the senderKey will not be unique if more than one participant in the
-     * conversation share the same name.
-     */
-    public SenderKey getSenderKey() {
-        return mSenderKey;
-    }
-
-    /** Returns whether the message is a SMS/MMS or a {@link NotificationMsg} **/
-    public MessageType getMessageType() {
-        return mMessageType;
-    }
-
-    /**
-     * Returns the sender's phone number available as a URI string.
-     * Note: MAP messages from iOS version 12 and earlier, as well as {@link MessagingStyleMessage},
-     * don't provide these.
-     */
-    @Nullable
-    public String getSenderContactUri() {
-        return mSenderContactUri;
-    }
-
-    @Override
-    public String toString() {
-        return "Message{"
-                + " mSenderName='" + mSenderName + '\''
-                + ", mMessageText='" + mMessageText + '\''
-                + ", mSenderContactUri='" + mSenderContactUri + '\''
-                + ", mReceiveTime=" + mReceivedTime + '\''
-                + ", mIsReadOnPhone= " + mIsReadOnPhone + '\''
-                + ", mShouldExclude= " + mShouldExclude + '\''
-                + ", mHandle='" + mHandle + '\''
-                + ", mSenderKey='" + mSenderKey.toString()
-                + "}";
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/MessageKey.java b/car-messenger-common/src/com/android/car/messenger/common/MessageKey.java
deleted file mode 100644
index 8244197..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/MessageKey.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.messenger.common;
-
-/**
- * {@link CompositeKey} subclass used to give each message on all the connected devices a
- * unique Key.
- **/
-public class MessageKey extends CompositeKey {
-
-    /** Creates a MessageKey for a {@link Message}. **/
-    public MessageKey(Message message) {
-        super(message.getDeviceId(), message.getHandle());
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/MessagingUtils.java b/car-messenger-common/src/com/android/car/messenger/common/MessagingUtils.java
new file mode 100644
index 0000000..8e9a2cd
--- /dev/null
+++ b/car-messenger-common/src/com/android/car/messenger/common/MessagingUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.messenger.common;
+
+import static com.android.car.assist.CarVoiceInteractionSession.KEY_DEVICE_ADDRESS;
+import static com.android.car.assist.CarVoiceInteractionSession.KEY_PHONE_NUMBER;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Utility class for text messaging related functions.
+ */
+public final class MessagingUtils {
+    private static final String TAG = "CMC.MessagingUtils";
+
+    /** Action used to direct send to a specified phone number */
+    public static final String ACTION_DIRECT_SEND = "ACTION_DIRECT_SEND";
+
+    private MessagingUtils() {}
+
+    /**
+     * Sends a reply, meant to be used from a caller originating from voice input.
+     *
+     * @param intent Originates from the client requesting to direct send a SMS, and may have been
+     *               modified by the system voice assistant.
+     */
+    public static void directSend(Context context, Intent intent) {
+        final CharSequence phoneNumber = intent.getCharSequenceExtra(KEY_PHONE_NUMBER);
+        final String iccId = intent.getStringExtra(KEY_DEVICE_ADDRESS);
+        final CharSequence message = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
+        if (iccId == null || phoneNumber == null || TextUtils.isEmpty(message)) {
+            Log.e(TAG, "Dropping voice reply. Received no icc id, number and/or empty message!");
+            return;
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Direct Send: id:" + iccId + ", pn:" + phoneNumber + ", msg:" + message);
+            Log.d(TAG, "Sending a message to specified phone number");
+        }
+
+        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+        List<SubscriptionInfo> infos = subManager.getActiveSubscriptionInfoList();
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "active subscriptions: " + infos);
+        }
+        if (infos == null) {
+            Log.w(TAG, "Dropping voice reply. No active subscriptions");
+            return;
+        }
+
+        SubscriptionInfo subInfo = infos.stream().filter(
+                info -> info.getIccId().equals(iccId)).findFirst().orElse(null);
+        if (subInfo == null) {
+            Log.w(TAG, "Dropping voice reply. No iccId matched");
+            return;
+        }
+
+        SmsManager smsManager;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            smsManager = context.getSystemService(SmsManager.class)
+                    .createForSubscriptionId(subInfo.getSubscriptionId());
+        } else {
+            smsManager = SmsManager.getSmsManagerForSubscriptionId(subInfo.getSubscriptionId());
+        }
+        smsManager.sendTextMessage(
+                phoneNumber.toString(),
+                /* scAddress= */ null,
+                message.toString(),
+                /* sentIntent= */ null,
+                /* deliveryIntent= */ null);
+    }
+}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/ProjectionStateListener.java b/car-messenger-common/src/com/android/car/messenger/common/ProjectionStateListener.java
deleted file mode 100644
index ddc8fac..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/ProjectionStateListener.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2020 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.messenger.common;
-
-import static com.android.car.apps.common.util.SafeLog.logd;
-import static com.android.car.apps.common.util.SafeLog.loge;
-import static com.android.car.apps.common.util.SafeLog.logi;
-
-import android.bluetooth.BluetoothDevice;
-import android.car.Car;
-import android.car.CarProjectionManager;
-import android.car.projection.ProjectionStatus;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Parcelable;
-
-import androidx.annotation.Nullable;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * {@link ProjectionStatus} listener that exposes APIs to detect whether a projection application
- * is active.
- */
-public class ProjectionStateListener {
-    private static final String TAG = "CMC.ProjectionStateHandler";
-    static final String PROJECTION_STATUS_EXTRA_DEVICE_STATE =
-            "android.car.projection.DEVICE_STATE";
-
-    private CarProjectionManager mCarProjectionManager = null;
-    private final CarProjectionManager.ProjectionStatusListener mListener =
-            (state, packageName, details) -> {
-                mProjectionState = state;
-                mProjectionDetails = details;
-            };
-    private Car mCar;
-
-    private int mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
-    private List<ProjectionStatus> mProjectionDetails = Collections.emptyList();
-
-    public ProjectionStateListener(Context context) {
-        Car.createCar(context, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
-                (car, ready) -> {
-                    mCar = car;
-                    mCarProjectionManager = (CarProjectionManager) mCar.getCarManager(
-                            Car.PROJECTION_SERVICE);
-                    if (mCarProjectionManager != null) {
-                        mCarProjectionManager.registerProjectionStatusListener(mListener);
-                    }
-                });
-    }
-
-    /** Unregisters the listener. Should be called when the caller's lifecycle is ending. **/
-    public void destroy() {
-        if (mCarProjectionManager != null) {
-            mCarProjectionManager.unregisterProjectionStatusListener(mListener);
-        }
-        if (mCar != null) {
-            mCar.disconnect();
-            mCar = null;
-        }
-        mProjectionState = ProjectionStatus.PROJECTION_STATE_INACTIVE;
-        mProjectionDetails = Collections.emptyList();
-    }
-
-    /**
-     * Returns {@code true} if the input device currently has a projection app running in the
-     * foreground.
-     * @param bluetoothAddress of the device that should be checked. If null, return whether any
-     *                         device is currently running a projection app in the foreground.
-     */
-    public boolean isProjectionInActiveForeground(@Nullable String bluetoothAddress) {
-        if (bluetoothAddress == null) {
-            logi(TAG, "returning non-device-specific projection status");
-            return isProjectionInActiveForeground();
-        }
-
-        if (!isProjectionInActiveForeground()) {
-            return false;
-        }
-
-        for (ProjectionStatus status : mProjectionDetails) {
-            if (!status.isActive()) {
-                // Don't suppress UI for packages that aren't actively projecting.
-                logd(TAG, "skip non-projecting package " + status.getPackageName());
-                continue;
-            }
-
-            for (ProjectionStatus.MobileDevice device : status.getConnectedMobileDevices()) {
-                if (!device.isProjecting()) {
-                    // Don't suppress UI for devices that aren't foreground.
-                    logd(TAG, "skip non-projecting device " + device.getName());
-                    continue;
-                }
-
-                Bundle extras = device.getExtras();
-                if (extras.getInt(PROJECTION_STATUS_EXTRA_DEVICE_STATE,
-                        ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND)
-                        != ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND) {
-                    logd(TAG, "skip device " + device.getName() + " - not foreground");
-                    continue;
-                }
-
-                Parcelable projectingBluetoothDevice =
-                        extras.getParcelable(BluetoothDevice.EXTRA_DEVICE);
-                logd(TAG, "Device " + device.getName() + " has BT device "
-                        + projectingBluetoothDevice);
-
-                if (projectingBluetoothDevice == null) {
-                    logi(TAG, "Suppressing message notification - device " + device
-                            + " is projection, and does not specify a Bluetooth address");
-                    return true;
-                } else if (!(projectingBluetoothDevice instanceof BluetoothDevice)) {
-                    loge(TAG, "Device " + device + " has bad EXTRA_DEVICE value "
-                            + projectingBluetoothDevice + " - treating as unspecified");
-                    return true;
-                } else if (bluetoothAddress.equals(
-                        ((BluetoothDevice) projectingBluetoothDevice).getAddress())) {
-                    logi(TAG, "Suppressing message notification - device " + device
-                            + "is projecting, and message is coming from device's Bluetooth address"
-                            + bluetoothAddress);
-                    return true;
-                }
-            }
-        }
-
-        // No projecting apps want to suppress this device, so let it through.
-        return false;
-    }
-
-    /** Returns {@code true} if a projection app is active in the foreground. **/
-    private boolean isProjectionInActiveForeground() {
-        return mProjectionState == ProjectionStatus.PROJECTION_STATE_ACTIVE_FOREGROUND;
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/SenderKey.java b/car-messenger-common/src/com/android/car/messenger/common/SenderKey.java
deleted file mode 100644
index 8e220b4..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/SenderKey.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.messenger.common;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg;
-
-/**
- * {@link CompositeKey} subclass used to give each contact on all the connected devices a
- * unique Key.
- */
-public class SenderKey extends CompositeKey {
-    /** Creates a senderkey for SMS, MMS, and {@link NotificationMsg}. **/
-    private SenderKey(String deviceId, String senderName, String keyMetadata) {
-        super(deviceId, senderName + "/" + keyMetadata);
-    }
-
-    /**
-     * Returns the SenderKey for the BluetoothMapClient intent. This should be unique
-     * for each contact per device.
-     */
-    public static SenderKey createSenderKey(Intent intent) {
-        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        return new SenderKey(device.getAddress(), Utils.getSenderName(intent),
-                Utils.getSenderUri(intent));
-    }
-
-    /**
-     * Returns the SenderKey based on a {@link NotificationMsg} DAO. This key is only
-     * guaranteed to be unique for a 1-1 conversation. If the ConversationKey is for a
-     * group conversation, the senderKey will not be unique if more than one participant in the
-     * conversation share the same name.
-     */
-    public static SenderKey createSenderKey(ConversationKey convoKey,
-            NotificationMsg.Person person) {
-        return new SenderKey(convoKey.getDeviceId(), person.getName(), convoKey.getSubKey());
-    }
-}
diff --git a/car-messenger-common/src/com/android/car/messenger/common/Utils.java b/car-messenger-common/src/com/android/car/messenger/common/Utils.java
deleted file mode 100644
index ccea454..0000000
--- a/car-messenger-common/src/com/android/car/messenger/common/Utils.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * 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.messenger.common;
-
-import static com.android.car.apps.common.util.SafeLog.logw;
-
-import android.bluetooth.BluetoothDevice;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
-
-import com.android.car.apps.common.LetterTileDrawable;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.AvatarIconSync;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.ConversationNotification;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyle;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.MessagingStyleMessage;
-import com.android.car.messenger.NotificationMsgProto.NotificationMsg.Person;
-
-import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.Phonenumber;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Utils methods for the car-messenger-common lib. **/
-public class Utils {
-    private static final String TAG = "CMC.Utils";
-    /**
-     * Represents the maximum length of a message substring to be used when constructing the
-     * message's unique handle/key.
-     */
-    private static final int MAX_SUB_MESSAGE_LENGTH = 5;
-
-    /** The Regex format of a telephone number in a BluetoothMapClient contact URI. **/
-    private static final String MAP_CLIENT_URI_REGEX = "tel:(.+)";
-
-    /** The starting substring index for a string formatted with the MAP_CLIENT_URI_REGEX above. **/
-    private static final int MAP_CLIENT_URI_PHONE_NUMBER_SUBSTRING_INDEX = 4;
-
-    // TODO (150711637): Reference BluetoothMapClient Extras once BluetoothMapClient is SystemApi.
-    protected static final String BMC_EXTRA_MESSAGE_HANDLE =
-            "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
-    protected static final String BMC_EXTRA_SENDER_CONTACT_URI =
-            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
-    protected static final String BMC_EXTRA_SENDER_CONTACT_NAME =
-            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
-    protected static final String BMC_EXTRA_MESSAGE_TIMESTAMP =
-            "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
-    protected static final String BMC_EXTRA_MESSAGE_READ_STATUS =
-            "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
-
-    /** Gets the latest message for a {@link NotificationMsg} Conversation. **/
-    public static MessagingStyleMessage getLatestMessage(
-            ConversationNotification notification) {
-        MessagingStyle messagingStyle = notification.getMessagingStyle();
-        long latestTime = 0;
-        MessagingStyleMessage latestMessage = null;
-
-        for (MessagingStyleMessage message : messagingStyle.getMessagingStyleMsgList()) {
-            if (message.getTimestamp() > latestTime) {
-                latestTime = message.getTimestamp();
-                latestMessage = message;
-            }
-        }
-        return latestMessage;
-    }
-
-    /**
-     * Helper method to create a unique handle/key for this message. This is used as this Message's
-     * {@link MessageKey#getSubKey()}.
-     */
-    public static String createMessageHandle(MessagingStyleMessage message) {
-        String textMessage = message.getTextMessage();
-        String subMessage = textMessage.substring(
-                Math.min(MAX_SUB_MESSAGE_LENGTH, textMessage.length()));
-        return message.getTimestamp() + "/" + message.getSender().getName() + "/" + subMessage;
-    }
-
-    /**
-     * Ensure the {@link ConversationNotification} object has all the required fields.
-     *
-     * @param isShallowCheck should be {@code true} if the caller only wants to verify the
-     *                       notification and its {@link MessagingStyle} is valid, without checking
-     *                       all of the notification's {@link MessagingStyleMessage}s.
-     **/
-    public static boolean isValidConversationNotification(ConversationNotification notification,
-            boolean isShallowCheck) {
-        if (notification == null) {
-            logw(TAG, "ConversationNotification is null");
-            return false;
-        } else if (!notification.hasMessagingStyle()) {
-            logw(TAG, "ConversationNotification is missing required field: messagingStyle");
-            return false;
-        } else if (notification.getMessagingAppDisplayName() == null) {
-            logw(TAG, "ConversationNotification is missing required field: appDisplayName");
-            return false;
-        } else if (notification.getMessagingAppPackageName() == null) {
-            logw(TAG, "ConversationNotification is missing required field: appPackageName");
-            return false;
-        }
-        return isValidMessagingStyle(notification.getMessagingStyle(), isShallowCheck);
-    }
-
-    /**
-     * Ensure the {@link MessagingStyle} object has all the required fields.
-     **/
-    private static boolean isValidMessagingStyle(MessagingStyle messagingStyle,
-            boolean isShallowCheck) {
-        if (messagingStyle == null) {
-            logw(TAG, "MessagingStyle is null");
-            return false;
-        } else if (messagingStyle.getConvoTitle() == null) {
-            logw(TAG, "MessagingStyle is missing required field: convoTitle");
-            return false;
-        } else if (messagingStyle.getUserDisplayName() == null) {
-            logw(TAG, "MessagingStyle is missing required field: userDisplayName");
-            return false;
-        } else if (messagingStyle.getMessagingStyleMsgCount() == 0) {
-            logw(TAG, "MessagingStyle is missing required field: messagingStyleMsg");
-            return false;
-        }
-        if (!isShallowCheck) {
-            for (MessagingStyleMessage message : messagingStyle.getMessagingStyleMsgList()) {
-                if (!isValidMessagingStyleMessage(message)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Ensure the {@link MessagingStyleMessage} object has all the required fields.
-     **/
-    public static boolean isValidMessagingStyleMessage(MessagingStyleMessage message) {
-        if (message == null) {
-            logw(TAG, "MessagingStyleMessage is null");
-            return false;
-        } else if (message.getTextMessage() == null) {
-            logw(TAG, "MessagingStyleMessage is missing required field: textMessage");
-            return false;
-        } else if (!message.hasSender()) {
-            logw(TAG, "MessagingStyleMessage is missing required field: sender");
-            return false;
-        }
-        return isValidSender(message.getSender());
-    }
-
-    /**
-     * Ensure the {@link Person} object has all the required fields.
-     **/
-    public static boolean isValidSender(Person person) {
-        if (person.getName() == null) {
-            logw(TAG, "Person is missing required field: name");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Ensure the {@link AvatarIconSync} object has all the required fields.
-     **/
-    public static boolean isValidAvatarIconSync(AvatarIconSync iconSync) {
-        if (iconSync == null) {
-            logw(TAG, "AvatarIconSync is null");
-            return false;
-        } else if (iconSync.getMessagingAppPackageName() == null) {
-            logw(TAG, "AvatarIconSync is missing required field: appPackageName");
-            return false;
-        } else if (iconSync.getPerson().getName() == null) {
-            logw(TAG, "AvatarIconSync is missing required field: Person's name");
-            return false;
-        } else if (iconSync.getPerson().getAvatar() == null) {
-            logw(TAG, "AvatarIconSync is missing required field: Person's avatar");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Ensure the BluetoothMapClient intent has all the required fields.
-     **/
-    public static boolean isValidMapClientIntent(Intent intent) {
-        if (intent == null) {
-            logw(TAG, "BluetoothMapClient intent is null");
-            return false;
-        } else if (intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) == null) {
-            logw(TAG, "BluetoothMapClient intent is missing required field: device");
-            return false;
-        } else if (intent.getStringExtra(BMC_EXTRA_MESSAGE_HANDLE) == null) {
-            logw(TAG, "BluetoothMapClient intent is missing required field: senderName");
-            return false;
-        } else if (intent.getStringExtra(BMC_EXTRA_SENDER_CONTACT_NAME) == null) {
-            logw(TAG, "BluetoothMapClient intent is missing required field: handle");
-            return false;
-        } else if (intent.getStringExtra(android.content.Intent.EXTRA_TEXT) == null) {
-            logw(TAG, "BluetoothMapClient intent is missing required field: messageText");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Creates a Letter Tile Icon that will display the given initials. If the initials are null,
-     * then an avatar anonymous icon will be drawn.
-     **/
-    public static Bitmap createLetterTile(Context context, @Nullable String initials,
-            String identifier, int avatarSize, float cornerRadiusPercent) {
-        // TODO(b/135446418): use TelecomUtils once car-telephony-common supports bp.
-        LetterTileDrawable letterTileDrawable = createLetterTileDrawable(context, initials,
-                identifier);
-        RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(
-                context.getResources(), letterTileDrawable.toBitmap(avatarSize));
-        return createFromRoundedBitmapDrawable(roundedBitmapDrawable, avatarSize,
-                cornerRadiusPercent);
-    }
-
-    /** Creates an Icon based on the given roundedBitmapDrawable. **/
-    private static Bitmap createFromRoundedBitmapDrawable(
-            RoundedBitmapDrawable roundedBitmapDrawable, int avatarSize,
-            float cornerRadiusPercent) {
-        // TODO(b/135446418): use TelecomUtils once car-telephony-common supports bp.
-        float radius = avatarSize * cornerRadiusPercent;
-        roundedBitmapDrawable.setCornerRadius(radius);
-
-        final Bitmap result = Bitmap.createBitmap(avatarSize, avatarSize,
-                Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(result);
-        roundedBitmapDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-        roundedBitmapDrawable.draw(canvas);
-        return roundedBitmapDrawable.getBitmap();
-    }
-
-
-    /**
-     * Create a {@link LetterTileDrawable} for the given initials.
-     *
-     * @param initials   is the letters that will be drawn on the canvas. If it is null, then an
-     *                   avatar anonymous icon will be drawn
-     * @param identifier will decide the color for the drawable. If null, a default color will be
-     *                   used.
-     */
-    private static LetterTileDrawable createLetterTileDrawable(
-            Context context,
-            @Nullable String initials,
-            @Nullable String identifier) {
-        // TODO(b/135446418): use TelecomUtils once car-telephony-common supports bp.
-        int numberOfLetter = context.getResources().getInteger(
-                R.integer.config_number_of_letters_shown_for_avatar);
-        String letters = initials != null
-                ? initials.substring(0, Math.min(initials.length(), numberOfLetter)) : null;
-        LetterTileDrawable letterTileDrawable = new LetterTileDrawable(context.getResources(),
-                letters, identifier);
-        return letterTileDrawable;
-    }
-
-    /** Returns whether the BluetoothMapClient intent represents a group conversation. **/
-    public static boolean isGroupConversation(Intent intent) {
-        return (intent.getStringArrayExtra(Intent.EXTRA_CC) != null
-                && intent.getStringArrayExtra(Intent.EXTRA_CC).length > 1);
-    }
-
-    /**
-     * Returns the initials based on the name and nameAlt.
-     *
-     * @param name    should be the display name of a contact.
-     * @param nameAlt should be alternative display name of a contact.
-     */
-    public static String getInitials(String name, String nameAlt) {
-        // TODO(b/135446418): use TelecomUtils once car-telephony-common supports bp.
-        StringBuilder initials = new StringBuilder();
-        if (!TextUtils.isEmpty(name) && Character.isLetter(name.charAt(0))) {
-            initials.append(Character.toUpperCase(name.charAt(0)));
-        }
-        if (!TextUtils.isEmpty(nameAlt)
-                && !TextUtils.equals(name, nameAlt)
-                && Character.isLetter(nameAlt.charAt(0))) {
-            initials.append(Character.toUpperCase(nameAlt.charAt(0)));
-        }
-        return initials.toString();
-    }
-
-    /** Returns the list of sender uri for a BluetoothMapClient intent. **/
-    public static String getSenderUri(Intent intent) {
-        return intent.getStringExtra(BMC_EXTRA_SENDER_CONTACT_URI);
-    }
-
-    /** Returns the sender name for a BluetoothMapClient intent. **/
-    public static String getSenderName(Intent intent) {
-        return intent.getStringExtra(BMC_EXTRA_SENDER_CONTACT_NAME);
-    }
-
-    /** Returns the list of recipient uris for a BluetoothMapClient intent. **/
-    public static List<String> getInclusiveRecipientsUrisList(Intent intent) {
-        List<String> ccUris = new ArrayList<>();
-        String uri = getSenderUri(intent);
-        if (isGroupConversation(intent)) {
-            ccUris.addAll(Arrays.asList(intent.getStringArrayExtra(Intent.EXTRA_CC)));
-        }
-        if (!ccUris.contains(uri)) {
-            ccUris.add(uri);
-        }
-
-        return ccUris;
-    }
-
-    /**
-     * Extracts the phone number from the BluetoothMapClient contact Uri.
-     **/
-    @Nullable
-    public static String getPhoneNumberFromMapClient(@Nullable String senderContactUri) {
-        if (senderContactUri == null || !senderContactUri.matches(MAP_CLIENT_URI_REGEX)) {
-            logw(TAG, " contactUri is malformed! " + senderContactUri);
-            return null;
-        }
-
-        return senderContactUri.substring(MAP_CLIENT_URI_PHONE_NUMBER_SUBSTRING_INDEX);
-    }
-
-    /**
-     * Creates a Header for a group conversation, where the senderName and groupName are both shown,
-     * separated by a delimiter.
-     *
-     * @param senderName Sender's name.
-     * @param groupName  Group conversation's name.
-     * @param delimiter  delimiter that separates each element.
-     */
-    public static String constructGroupConversationHeader(String senderName, String groupName,
-            String delimiter) {
-        return constructGroupConversationHeader(senderName, groupName, delimiter,
-                BidiFormatter.getInstance());
-    }
-
-    /**
-     * Creates a Header for a group conversation, where the senderName and groupName are both shown,
-     * separated by a delimiter.
-     *
-     * @param senderName Sender's name.
-     * @param groupName  Group conversation's name.
-     * @param delimiter  delimiter that separates each element.
-     * @param bidiFormatter  formatter for the context's locale.
-     */
-    public static String constructGroupConversationHeader(String senderName, String groupName,
-            String delimiter, BidiFormatter bidiFormatter) {
-        String formattedSenderName = bidiFormatter.unicodeWrap(senderName,
-                TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
-        String formattedGroupName = bidiFormatter.unicodeWrap(groupName,
-                TextDirectionHeuristics.FIRSTSTRONG_LTR, /* isolate= */ true);
-        String title = String.join(delimiter, formattedSenderName, formattedGroupName);
-        return bidiFormatter.unicodeWrap(title, TextDirectionHeuristics.LOCALE);
-    }
-
-    /**
-     * Given a name of all the participants in a group conversation (some names might be phone
-     * numbers), this function creates the conversation title by putting the names in alphabetical
-     * order first, then adding any phone numbers. This title should not exceed the
-     * conversationTitleLength, so not all participants' names are guaranteed to be
-     * in the conversation title.
-     */
-    public static String constructGroupConversationTitle(List<String> names, String delimiter,
-            int conversationTitleLength) {
-        return constructGroupConversationTitle(names, delimiter, conversationTitleLength,
-                BidiFormatter.getInstance());
-    }
-
-    /**
-     * Given a name of all the participants in a group conversation (some names might be phone
-     * numbers), this function creates the conversation title by putting the names in alphabetical
-     * order first, then adding any phone numbers. This title should not exceed the
-     * conversationTitleLength, so not all participants' names are guaranteed to be
-     * in the conversation title.
-     */
-    public static String constructGroupConversationTitle(List<String> names, String delimiter,
-            int conversationTitleLength, BidiFormatter bidiFormatter) {
-        List<String> sortedNames = getSortedSubsetNames(names, conversationTitleLength,
-                delimiter.length());
-        String formattedDelimiter = bidiFormatter.unicodeWrap(delimiter,
-                TextDirectionHeuristics.LOCALE);
-
-        String conversationName = sortedNames.stream().map(name -> bidiFormatter.unicodeWrap(name,
-                TextDirectionHeuristics.FIRSTSTRONG_LTR))
-                .collect(Collectors.joining(formattedDelimiter));
-        return bidiFormatter.unicodeWrap(conversationName, TextDirectionHeuristics.LOCALE);
-    }
-
-    /**
-     * Sorts the list, and returns the first elements whose total length is less than the given
-     * conversationTitleLength.
-     */
-    private static List<String> getSortedSubsetNames(List<String> names,
-            int conversationTitleLength,
-            int delimiterLength) {
-        Collections.sort(names, Utils.ALPHA_THEN_NUMERIC_COMPARATOR);
-        int namesCounter = 0;
-        int indexCounter = 0;
-        while (namesCounter < conversationTitleLength && indexCounter < names.size()) {
-            namesCounter = namesCounter + names.get(indexCounter).length() + delimiterLength;
-            indexCounter = indexCounter + 1;
-        }
-        return names.subList(0, indexCounter);
-    }
-
-    /** Comparator that sorts names alphabetically first, then phone numbers numerically. **/
-    public static final Comparator<String> ALPHA_THEN_NUMERIC_COMPARATOR =
-            new Comparator<String>() {
-                private boolean isPhoneNumber(String input) {
-                    PhoneNumberUtil util = PhoneNumberUtil.getInstance();
-                    try {
-                        Phonenumber.PhoneNumber phoneNumber = util.parse(input, /* defaultRegion */
-                                null);
-                        return util.isValidNumber(phoneNumber);
-                    } catch (NumberParseException e) {
-                        // Phone numbers without country codes should still be classified as
-                        // phone numbers.
-                        return e.getErrorType().equals(
-                                NumberParseException.ErrorType.INVALID_COUNTRY_CODE);
-                    }
-                }
-
-                private boolean isOfSameType(String o1, String o2) {
-                    boolean isO1PhoneNumber = isPhoneNumber(o1);
-                    boolean isO2PhoneNumber = isPhoneNumber(o2);
-                    return isO1PhoneNumber == isO2PhoneNumber;
-                }
-
-                @Override
-                public int compare(String o1, String o2) {
-                    // if both are names, sort based on names.
-                    // if both are number, sort numerically.
-                    // if one is phone number and the other is a name, give name precedence.
-                    if (!isOfSameType(o1, o2)) {
-                        return isPhoneNumber(o1) ? 1 : -1;
-                    } else {
-                        return o1.compareTo(o2);
-                    }
-                }
-            };
-
-}
diff --git a/car-messenger-common/tests/unit/AndroidManifest.xml b/car-messenger-common/tests/unit/AndroidManifest.xml
deleted file mode 100644
index eefce32..0000000
--- a/car-messenger-common/tests/unit/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-  ~ Copyright (C) 2020 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.messenger.common.tests.unit">
-    <uses-permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
-    <application android:testOnly="true"
-                 android:debuggable="true"
-                 xmlns:tools="http://schemas.android.com/tools">
-        <uses-library android:name="android.test.runner" />
-        <!-- Workaround for b/113294940 -->
-        <provider
-            android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
-            tools:replace="android:authorities"
-            android:authorities="${applicationId}.lifecycle"
-            android:exported="false"
-            android:multiprocess="true" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.car.messenger.common.tests.unit"
-                     android:label="Car Messenger Lib Test Cases" />
-</manifest>
\ No newline at end of file
diff --git a/car-messenger-common/tests/unit/README.md b/car-messenger-common/tests/unit/README.md
deleted file mode 100644
index 12bb628..0000000
--- a/car-messenger-common/tests/unit/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions for running unit tests
-
-### Build unit test module
-
-`m car-messenger-common-lib-unit-tests`
-
-### Install resulting apk on device
-
-`adb install -r -t $OUT/testcases/car-messenger-common-lib-unit-tests/arm64/car-messenger-common-lib-unit-tests.apk`
-
-### Run all tests
-
-`adb shell am instrument -w com.android.car.messenger.common.tests.unit`
-
-### Run tests in a class
-
-`adb shell am instrument -w -e class com.android.car.messenger.common.<classPath> com.android.car.messenger.common.tests.unit`
-
-### Run a specific test
-
-`adb shell am instrument -w -e class com.android.car.messenger.common.<classPath>#<testMethod> com.android.car.messenger.common.tests.unit`
-
-More general information can be found at
-http://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html
\ No newline at end of file
diff --git a/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java b/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
deleted file mode 100644
index c64703a..0000000
--- a/car-messenger-common/tests/unit/src/com.android.car.messenger.common/UtilsTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2020 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.messenger.common;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.text.BidiFormatter;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class UtilsTest {
-
-    private static final String ARABIC_NAME = "جﺗﺧ";
-    private static final List<String> NAMES = Arrays.asList("+1-650-900-1234", "Logan.", "Emily",
-            "Christopher", "!Sam", ARABIC_NAME);
-    private static final String NAME_DELIMITER = "، ";
-    private static final String TITLE_DELIMITER = " : ";
-    private static final int TITLE_LENGTH = 30;
-    private static final BidiFormatter RTL_FORMATTER = BidiFormatter.getInstance(/* rtlContext= */
-            true);
-
-    @Test
-    public void testNameWithMultipleNumbers() {
-        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
-        List<String> senderNames = Arrays.asList("+1-650-900-1234", "+1-650-900-1111",
-                "+1-100-200-1234");
-        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
-                TITLE_LENGTH + 20);
-        String expected = "+1-100-200-1234، +1-650-900-1111، +1-650-900-1234";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testNameWithInternationalNumbers() {
-        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
-        List<String> senderNames = Arrays.asList("+44-20-7183-8750", "+1-650-900-1111",
-                "+1-100-200-1234");
-        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
-                TITLE_LENGTH + 20);
-        String expected = "+1-100-200-1234، +1-650-900-1111، +44-20-7183-8750";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testNameConstructorLtr() {
-        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER, TITLE_LENGTH);
-        assertThat(actual).isEqualTo("!Sam، Christopher، Emily، Logan.");
-    }
-
-    @Test
-    public void testNameConstructorLtr_longerTitle() {
-        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER,
-                TITLE_LENGTH + 5);
-        assertThat(actual).isEqualTo(
-                "!Sam، Christopher، Emily، Logan.، \u200E\u202Bجﺗﺧ\u202C\u200E");
-
-    }
-
-    @Test
-    public void testTitleConstructorLtr() {
-        String actual = Utils.constructGroupConversationHeader("Christopher",
-                "!Sam، Emily، Logan.، +1-650-900-1234", TITLE_DELIMITER);
-        String expected = "Christopher : !Sam، Emily، Logan.، +1-650-900-1234";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testTitleConstructorLtr_with_rtlName() {
-        String actual = Utils.constructGroupConversationHeader(ARABIC_NAME, "!Sam، Logan.، جﺗﺧ",
-                TITLE_DELIMITER);
-        // Note: the Group name doesn't have the RTL tag because in the function we format the
-        // entire group name string, not each name in the string.
-        String expected = "\u200E\u202Bجﺗﺧ\u202C\u200E : !Sam، Logan.، جﺗﺧ\u200E";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testTitleConstructorLtr_with_phoneNumber() {
-        String actual = Utils.constructGroupConversationHeader("+1-650-900-1234",
-                "!Sam، Logan.، جﺗﺧ",
-                TITLE_DELIMITER);
-        // Note: the Group name doesn't have the RTL tag because in the function we format the
-        // entire group name string, not each name in the string.
-        String expected = "+1-650-900-1234 : !Sam، Logan.، جﺗﺧ\u200E";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    /**
-     * NOTE for all the RTL tests done below: When BidiFormatter is unicode-wrapping strings, they
-     * are actually adding invisible Unicode characters to denote whether a section is RTL, LTR,
-     * etc. These invisible characters are NOT visible on the terminal output, or if you copy
-     * paste the string to most HTML pages. They ARE visible when you paste them in certain
-     * text editors like IntelliJ, or there are some online tools that provide this as well.
-     *
-     * Therefore, in most of these RTL tests (and some of the LTR tests) you will see the
-     * invisible characters in the expected strings. Here's a couple of the characters, and what
-     * they're used for:
-     * \u200F is the RTL mark
-     * \u200E is the LTR mark
-     * \u202A marks the start of LTR embedding
-     * \u202B marks the start of RTL embedding
-     * \u202C pops the directional formatting - Must be used to end an embedding
-     */
-    @Test
-    public void testNameWithInternationalNumbers_rtl() {
-        // Ensure that a group name with many phone numbers sorts the phone numbers correctly.
-        List<String> senderNames = Arrays.asList("+44-20-7183-8750", "+1-650-900-1111",
-                "+1-100-200-1234");
-        String actual = Utils.constructGroupConversationTitle(senderNames, NAME_DELIMITER,
-                TITLE_LENGTH + 20, RTL_FORMATTER);
-        String expected = "\u200F\u202A\u200F\u202A+1-100-200-1234\u202C\u200F\u200F\u202A، "
-                + "\u202C\u200F\u200F\u202A+1-650-900-1111\u202C\u200F\u200F\u202A، "
-                + "\u202C\u200F\u200F\u202A+44-20-7183-8750\u202C\u200F\u202C\u200F";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testNameConstructorRtl() {
-        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER, TITLE_LENGTH,
-                /* isRtl */ RTL_FORMATTER);
-
-        String expected =
-                "\u200F\u202A\u200F\u202A!Sam\u202C\u200F\u200F\u202A، \u202C\u200F"
-                        + "\u200F\u202AChristopher\u202C\u200F\u200F\u202A، \u202C\u200F"
-                        + "\u200F\u202AEmily\u202C\u200F\u200F\u202A، "
-                        + "\u202C\u200F\u200F\u202ALogan.\u202C\u200F\u202C\u200F";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testNameConstructorRtl_longerTitle() {
-        String actual = Utils.constructGroupConversationTitle(NAMES, NAME_DELIMITER,
-                TITLE_LENGTH + 5, /* isRtl */ RTL_FORMATTER);
-
-        String expected =
-                "\u200F\u202A\u200F\u202A!Sam\u202C\u200F\u200F\u202A، "
-                        + "\u202C\u200F\u200F\u202AChristopher\u202C\u200F\u200F"
-                        + "\u202A، \u202C\u200F\u200F\u202AEmily\u202C\u200F\u200F\u202A، "
-                        + "\u202C\u200F\u200F\u202ALogan.\u202C\u200F\u200F\u202A، "
-                        + "\u202C\u200Fجﺗﺧ\u202C\u200F";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testTitleConstructorRtl_with_rtlName() {
-        String actual = Utils.constructGroupConversationHeader(ARABIC_NAME, "!Sam، Logan.، جﺗﺧ",
-                TITLE_DELIMITER, RTL_FORMATTER);
-        // Note: the Group name doesn't have the RTL tag because in the function we format the
-        // entire group name string, not each name in the string.
-        // Also, note that the sender's name, which is RTL still has LTR embedded because we wrap
-        // it with FIRSTSTRONG_LTR.
-        String expected = "\u200F\u202Aجﺗﺧ : \u200F\u202A!Sam، Logan.، جﺗﺧ\u202C\u200F\u202C"
-                + "\u200F";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-
-    @Test
-    public void testTitleConstructorRtl_with_phoneNumber() {
-        String actual = Utils.constructGroupConversationHeader("+1-650-900-1234",
-                "!Sam، Logan.، جﺗﺧ",
-                TITLE_DELIMITER, RTL_FORMATTER);
-        // Note: the Group name doesn't have the RTL tag because in the function we format the
-        // entire group name string, not each name in the string.
-        String expected = "\u200F\u202A\u200F\u202A+1-650-900-1234\u202C\u200F : "
-                + "\u200F\u202A!Sam، Logan.، جﺗﺧ\u202C\u200F\u202C\u200F";
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testTitleConstructorRtl() {
-        String actual = Utils.constructGroupConversationHeader("Christopher",
-                "+1-650-900-1234، Logan.، Emily، Christopher، !Sam", TITLE_DELIMITER, /* isRtl */
-                RTL_FORMATTER).trim();
-
-        String expected =
-                "\u200F\u202A\u200F\u202AChristopher\u202C\u200F : \u200F\u202A+1-650-900-1234، "
-                        + "Logan.، Emily، Christopher، !Sam\u202C\u200F\u202C\u200F";
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void testTitleConstructorRtl_withTrailingPunctuation() {
-        String actual = Utils.constructGroupConversationHeader("Christopher",
-                "Abcd!!!", TITLE_DELIMITER, /* isRtl */
-                RTL_FORMATTER).trim();
-
-        String expected =
-                "\u200F\u202A\u200F\u202AChristopher\u202C\u200F : \u200F\u202AAbcd!!!"
-                        + "\u202C\u200F\u202C\u200F";
-
-        assertThat(actual).isEqualTo(expected);
-    }
-}
diff --git a/car-theme-lib/Android.bp b/car-qc-lib/Android.bp
similarity index 79%
rename from car-theme-lib/Android.bp
rename to car-qc-lib/Android.bp
index d2b8772..c12fd28 100644
--- a/car-theme-lib/Android.bp
+++ b/car-qc-lib/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2021 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.
@@ -12,20 +12,20 @@
 // 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 {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_library {
-    name: "car-theme-lib",
-
+    name: "car-qc-lib",
+    platform_apis: true,
     srcs: ["src/**/*.java"],
-
-    resource_dirs: ["res"],
-
     optimize: {
         enabled: false,
     },
+    static_libs: [
+        "androidx.annotation_annotation",
+        "car-ui-lib"
+    ],
 }
diff --git a/car-messenger-common/AndroidManifest.xml b/car-qc-lib/AndroidManifest.xml
similarity index 77%
rename from car-messenger-common/AndroidManifest.xml
rename to car-qc-lib/AndroidManifest.xml
index 148737d..166d9c0 100644
--- a/car-messenger-common/AndroidManifest.xml
+++ b/car-qc-lib/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,8 +14,6 @@
   ~ 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.messenger.common">
-    <uses-permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
+          package="com.android.car.qc">
 </manifest>
diff --git a/car-qc-lib/OWNERS b/car-qc-lib/OWNERS
new file mode 100644
index 0000000..7f8081c
--- /dev/null
+++ b/car-qc-lib/OWNERS
@@ -0,0 +1,8 @@
+# People who can approve changes for submission.
+
+# Primary
+alexstetson@google.com
+
+# Secondary (only if people in Primary are unreachable)
+hseog@google.com
+nehah@google.com
diff --git a/car-qc-lib/PREUPLOAD.cfg b/car-qc-lib/PREUPLOAD.cfg
new file mode 100644
index 0000000..38f9800
--- /dev/null
+++ b/car-qc-lib/PREUPLOAD.cfg
@@ -0,0 +1,7 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
+[Builtin Hooks]
+commit_msg_changeid_field = true
+commit_msg_test_field = true
diff --git a/car-qc-lib/res/color/qc_toggle_background_color.xml b/car-qc-lib/res/color/qc_toggle_background_color.xml
new file mode 100644
index 0000000..15253ad
--- /dev/null
+++ b/car-qc-lib/res/color/qc_toggle_background_color.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="false" android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@color/qc_toggle_off_background_color"/>
+    <item android:state_checked="false"
+          android:color="@color/qc_toggle_off_background_color"/>
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/colorAccent"/>
+    <item android:color="?android:attr/colorAccent"/>
+</selector>
diff --git a/car-qc-lib/res/color/qc_toggle_icon_fill_color.xml b/car-qc-lib/res/color/qc_toggle_icon_fill_color.xml
new file mode 100644
index 0000000..bdb5433
--- /dev/null
+++ b/car-qc-lib/res/color/qc_toggle_icon_fill_color.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="false" android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@android:color/white"/>
+    <item android:state_checked="false"
+          android:color="@android:color/white"/>
+    <item android:state_enabled="false"
+          android:alpha="?android:attr/disabledAlpha"
+          android:color="@android:color/black"/>
+    <item android:color="@android:color/black"/>
+</selector>
diff --git a/car-messenger-common/res/values/dimens.xml b/car-qc-lib/res/drawable/qc_row_action_divider.xml
similarity index 68%
copy from car-messenger-common/res/values/dimens.xml
copy to car-qc-lib/res/drawable/qc_row_action_divider.xml
index ea87725..75ffd46 100644
--- a/car-messenger-common/res/values/dimens.xml
+++ b/car-qc-lib/res/drawable/qc_row_action_divider.xml
@@ -1,6 +1,5 @@
-<?xml version='1.0' encoding='UTF-8'?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,7 +13,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<resources>
-    <dimen name="notification_contact_photo_size">300dp</dimen>
-    <dimen name="contact_avatar_corner_radius_percent" format="float">0.5</dimen>
-</resources>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <size
+        android:height="0dp"
+        android:width="@dimen/qc_toggle_margin"/>
+</shape>
diff --git a/car-qc-lib/res/drawable/qc_seekbar_wrapper_background.xml b/car-qc-lib/res/drawable/qc_seekbar_wrapper_background.xml
new file mode 100644
index 0000000..58b9c65
--- /dev/null
+++ b/car-qc-lib/res/drawable/qc_seekbar_wrapper_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Highlight the wrapper when it's focused but not selected. The wrapper is selected in
+    direct manipulation mode. -->
+    <item android:state_focused="true" android:state_selected="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_stroke_color"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/car-qc-lib/res/drawable/qc_toggle_background.xml b/car-qc-lib/res/drawable/qc_toggle_background.xml
new file mode 100644
index 0000000..c139590
--- /dev/null
+++ b/car-qc-lib/res/drawable/qc_toggle_background.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background"
+          android:width="@dimen/qc_toggle_size"
+          android:height="@dimen/qc_toggle_size">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/qc_toggle_background_color" />
+            <corners android:radius="@dimen/qc_toggle_background_radius" />
+        </shape>
+    </item>
+    <item android:width="@dimen/qc_toggle_size"
+          android:height="@dimen/qc_toggle_size"
+          android:drawable="@drawable/qc_toggle_rotary_background"/>
+</layer-list>
\ No newline at end of file
diff --git a/car-qc-lib/res/drawable/qc_toggle_rotary_background.xml b/car-qc-lib/res/drawable/qc_toggle_rotary_background.xml
new file mode 100644
index 0000000..406c44c
--- /dev/null
+++ b/car-qc-lib/res/drawable/qc_toggle_rotary_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_pressed_fill_secondary_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_pressed_stroke_secondary_color"/>
+            <corners android:radius="@dimen/qc_toggle_rotary_background_radius" />
+        </shape>
+    </item>
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/car_ui_rotary_focus_fill_secondary_color"/>
+            <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width"
+                    android:color="@color/car_ui_rotary_focus_stroke_secondary_color"/>
+            <corners android:radius="@dimen/qc_toggle_rotary_background_radius" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
new file mode 100644
index 0000000..98cbded
--- /dev/null
+++ b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background"
+          android:width="@dimen/qc_toggle_size"
+          android:height="@dimen/qc_toggle_size">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/qc_toggle_unavailable_background_color" />
+            <stroke android:color="@color/qc_toggle_unavailable_color"
+                    android:width="@dimen/qc_toggle_unavailable_outline_width" />
+            <corners android:radius="@dimen/qc_toggle_background_radius" />
+        </shape>
+    </item>
+    <item android:width="@dimen/qc_toggle_size"
+          android:height="@dimen/qc_toggle_size"
+          android:drawable="@drawable/qc_toggle_rotary_background"/>
+</layer-list>
diff --git a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml b/car-qc-lib/res/layout/qc_action_switch.xml
similarity index 74%
copy from car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
copy to car-qc-lib/res/layout/qc_action_switch.xml
index ec6318a..d51b61d 100644
--- a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
+++ b/car-qc-lib/res/layout/qc_action_switch.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,10 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape
+<Switch
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="@color/car_ui_scrollbar_thumb" />
-    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
-</shape>
+    android:id="@android:id/switch_widget"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
+
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-qc-lib/res/layout/qc_action_toggle.xml
similarity index 62%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-qc-lib/res/layout/qc_action_toggle.xml
index 6a35b43..301e0c4 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-qc-lib/res/layout/qc_action_toggle.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,11 +14,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<FrameLayout
+<com.android.car.ui.uxr.DrawableStateToggleButton
     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>
+    android:id="@+id/qc_toggle_button"
+    android:background="@android:color/transparent"
+    android:defaultFocusHighlightEnabled="false"
+    android:minHeight="0dp"
+    android:minWidth="0dp"
+    android:layout_width="@dimen/qc_toggle_size"
+    android:layout_height="@dimen/qc_toggle_size"/>
\ No newline at end of file
diff --git a/car-qc-lib/res/layout/qc_row_view.xml b/car-qc-lib/res/layout/qc_row_view.xml
new file mode 100644
index 0000000..5975426
--- /dev/null
+++ b/car-qc-lib/res/layout/qc_row_view.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.car.ui.uxr.DrawableStateConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_centerVertical="true"
+    android:layout_marginVertical="@dimen/qc_row_margin_vertical"
+    android:clipToPadding="false"
+    android:minHeight="@dimen/qc_row_min_height"
+    android:paddingEnd="@dimen/qc_row_padding_end"
+    android:paddingStart="@dimen/qc_row_padding_start">
+
+    <LinearLayout
+        android:id="@+id/qc_row_start_items"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/qc_action_items_horizontal_margin"
+        android:orientation="horizontal"
+        android:divider="@drawable/qc_row_action_divider"
+        android:showDividers="middle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/qc_row_content"
+        app:layout_constraintHorizontal_chainStyle="spread_inside"/>
+
+    <com.android.car.ui.uxr.DrawableStateConstraintLayout
+        android:id="@+id/qc_row_content"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="?android:attr/selectableItemBackground"
+        app:layout_constraintStart_toEndOf="@+id/qc_row_start_items"
+        app:layout_constraintEnd_toStartOf="@+id/qc_row_end_items"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintHeight_default="wrap"
+        app:layout_constraintHeight_min="@dimen/qc_row_min_height">
+
+        <com.android.car.ui.uxr.DrawableStateImageView
+            android:id="@+id/qc_icon"
+            android:layout_width="@dimen/qc_row_icon_size"
+            android:layout_height="@dimen/qc_row_icon_size"
+            android:layout_marginEnd="@dimen/qc_row_icon_margin_end"
+            android:scaleType="fitCenter"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/barrier1"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@+id/barrier2"/>
+
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/barrier1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:barrierDirection="end"
+            app:constraint_referenced_ids="qc_icon"
+            app:barrierAllowsGoneWidgets="false"/>
+
+        <com.android.car.ui.uxr.DrawableStateTextView
+            android:id="@+id/qc_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.QC.Title"
+            app:layout_constraintStart_toEndOf="@+id/barrier1"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@+id/qc_summary"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintVertical_chainStyle="packed"/>
+
+        <com.android.car.ui.uxr.DrawableStateTextView
+            android:id="@+id/qc_summary"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:textAppearance="@style/TextAppearance.QC.Subtitle"
+            app:layout_constraintStart_toEndOf="@+id/barrier1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/qc_title"
+            app:layout_constraintBottom_toTopOf="@+id/barrier2"/>
+
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/barrier2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:barrierDirection="top"
+            app:constraint_referenced_ids="qc_seekbar_wrapper"
+            app:barrierAllowsGoneWidgets="false"/>
+
+        <androidx.preference.UnPressableLinearLayout
+            android:id="@+id/qc_seekbar_wrapper"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/qc_seekbar_padding_top"
+            android:focusable="true"
+            android:background="@drawable/qc_seekbar_wrapper_background"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:layout_centerVertical="true"
+            android:orientation="vertical"
+            android:visibility="gone"
+            app:layout_constraintStart_toEndOf="@+id/barrier1"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/barrier2"
+            app:layout_constraintBottom_toBottomOf="parent">
+            <SeekBar
+                android:id="@+id/seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="@style/Widget.QC.SeekBar"/>
+        </androidx.preference.UnPressableLinearLayout>
+
+    </com.android.car.ui.uxr.DrawableStateConstraintLayout>
+
+    <LinearLayout
+        android:id="@+id/qc_row_end_items"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qc_action_items_horizontal_margin"
+        android:orientation="horizontal"
+        android:divider="@drawable/qc_row_action_divider"
+        android:showDividers="middle"
+        app:layout_constraintStart_toEndOf="@+id/qc_row_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
+</com.android.car.ui.uxr.DrawableStateConstraintLayout>
diff --git a/car-qc-lib/res/layout/qc_tile_view.xml b/car-qc-lib/res/layout/qc_tile_view.xml
new file mode 100644
index 0000000..7fb0884
--- /dev/null
+++ b/car-qc-lib/res/layout/qc_tile_view.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.car.ui.uxr.DrawableStateLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/qc_tile_wrapper"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="vertical"
+    android:background="?android:attr/selectableItemBackground">
+    <com.android.car.ui.uxr.DrawableStateToggleButton
+        android:id="@+id/qc_tile_toggle_button"
+        android:background="@android:color/transparent"
+        android:layout_width="@dimen/qc_toggle_size"
+        android:layout_height="@dimen/qc_toggle_size"
+        android:layout_marginTop="@dimen/qc_toggle_margin"
+        android:layout_marginBottom="@dimen/qc_toggle_margin"
+        android:layout_marginStart="@dimen/qc_toggle_margin"
+        android:layout_marginEnd="@dimen/qc_toggle_margin"
+        android:clickable="false"
+        android:focusable="false"/>
+    <com.android.car.ui.uxr.DrawableStateTextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.QC.Subtitle"/>
+</com.android.car.ui.uxr.DrawableStateLinearLayout>
\ No newline at end of file
diff --git a/car-messenger-common/res/values/dimens.xml b/car-qc-lib/res/values/colors.xml
similarity index 63%
copy from car-messenger-common/res/values/dimens.xml
copy to car-qc-lib/res/values/colors.xml
index ea87725..e3fbd6f 100644
--- a/car-messenger-common/res/values/dimens.xml
+++ b/car-qc-lib/res/values/colors.xml
@@ -1,6 +1,5 @@
-<?xml version='1.0' encoding='UTF-8'?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,7 +13,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <resources>
-    <dimen name="notification_contact_photo_size">300dp</dimen>
-    <dimen name="contact_avatar_corner_radius_percent" format="float">0.5</dimen>
-</resources>
+    <color name="qc_start_icon_color">@android:color/white</color>
+    <color name="qc_toggle_off_background_color">#626262</color>
+    <color name="qc_toggle_unavailable_background_color">@android:color/transparent</color>
+    <color name="qc_toggle_unavailable_color">#75FFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/car-qc-lib/res/values/dimens.xml b/car-qc-lib/res/values/dimens.xml
new file mode 100644
index 0000000..6247561
--- /dev/null
+++ b/car-qc-lib/res/values/dimens.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="qc_row_padding_start">32dp</dimen>
+    <dimen name="qc_row_padding_end">32dp</dimen>
+    <dimen name="qc_row_min_height">76dp</dimen>
+    <dimen name="qc_row_margin_vertical">10dp</dimen>
+    <dimen name="qc_row_icon_size">44dp</dimen>
+    <dimen name="qc_row_icon_margin_end">32dp</dimen>
+    <dimen name="qc_row_content_margin">16dp</dimen>
+
+    <dimen name="qc_action_items_horizontal_margin">32dp</dimen>
+    <dimen name="qc_toggle_size">72dp</dimen>
+    <dimen name="qc_toggle_margin">12dp</dimen>
+    <dimen name="qc_toggle_background_radius">16dp</dimen>
+    <dimen name="qc_toggle_rotary_background_radius">11dp</dimen>
+    <dimen name="qc_toggle_foreground_icon_inset">14dp</dimen>
+    <dimen name="qc_toggle_unavailable_outline_width">2dp</dimen>
+
+    <dimen name="qc_seekbar_padding_top">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/car-qc-lib/res/values/styles.xml b/car-qc-lib/res/values/styles.xml
new file mode 100644
index 0000000..587b522
--- /dev/null
+++ b/car-qc-lib/res/values/styles.xml
@@ -0,0 +1,39 @@
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <style name="TextAppearance.QC" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textColor">@color/car_ui_text_color_primary</item>
+    </style>
+
+    <style name="TextAppearance.QC.Title">
+        <item name="android:textSize">@dimen/car_ui_body1_size</item>
+    </style>
+
+    <style name="TextAppearance.QC.Subtitle">
+        <item name="android:textColor">@color/car_ui_text_color_secondary</item>
+        <item name="android:textSize">@dimen/car_ui_body3_size</item>
+    </style>
+
+    <style name="Widget.QC" parent="android:Widget.DeviceDefault"/>
+
+    <style name="Widget.QC.SeekBar">
+        <item name="android:background">@null</item>
+        <item name="android:clickable">false</item>
+        <item name="android:focusable">false</item>
+        <item name="android:splitTrack">false</item>
+    </style>
+</resources>
diff --git a/car-qc-lib/src/com/android/car/qc/QCActionItem.java b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
new file mode 100644
index 0000000..4c66bea
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Quick Control Action that are includes as either start or end actions in {@link QCRow}
+ */
+public class QCActionItem extends QCItem {
+    private final boolean mIsChecked;
+    private final boolean mIsEnabled;
+    private final boolean mIsAvailable;
+    private Icon mIcon;
+    private PendingIntent mAction;
+
+    public QCActionItem(@NonNull @QCItemType String type, boolean isChecked, boolean isEnabled,
+            boolean isAvailable, @Nullable Icon icon, @Nullable PendingIntent action) {
+        super(type);
+        mIsEnabled = isEnabled;
+        mIsChecked = isChecked;
+        mIsAvailable = isAvailable;
+        mIcon = icon;
+        mAction = action;
+    }
+
+    public QCActionItem(@NonNull Parcel in) {
+        super(in);
+        mIsChecked = in.readBoolean();
+        mIsEnabled = in.readBoolean();
+        mIsAvailable = in.readBoolean();
+        boolean hasIcon = in.readBoolean();
+        if (hasIcon) {
+            mIcon = Icon.CREATOR.createFromParcel(in);
+        }
+        boolean hasAction = in.readBoolean();
+        if (hasAction) {
+            mAction = PendingIntent.CREATOR.createFromParcel(in);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeBoolean(mIsChecked);
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mIsAvailable);
+        boolean includeIcon = getType().equals(QC_TYPE_ACTION_TOGGLE) && mIcon != null;
+        dest.writeBoolean(includeIcon);
+        if (includeIcon) {
+            mIcon.writeToParcel(dest, flags);
+        }
+        boolean hasAction = mAction != null;
+        dest.writeBoolean(hasAction);
+        if (hasAction) {
+            mAction.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public PendingIntent getPrimaryAction() {
+        return mAction;
+    }
+
+    public boolean isChecked() {
+        return mIsChecked;
+    }
+
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    public boolean isAvailable() {
+        return mIsAvailable;
+    }
+
+    @Nullable
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    public static Creator<QCActionItem> CREATOR = new Creator<QCActionItem>() {
+        @Override
+        public QCActionItem createFromParcel(Parcel source) {
+            return new QCActionItem(source);
+        }
+
+        @Override
+        public QCActionItem[] newArray(int size) {
+            return new QCActionItem[size];
+        }
+    };
+
+    /**
+     * Builder for {@link QCActionItem}.
+     */
+    public static class Builder {
+        private final String mType;
+        private boolean mIsChecked;
+        private boolean mIsEnabled = true;
+        private boolean mIsAvailable = true;
+        private Icon mIcon;
+        private PendingIntent mAction;
+
+        public Builder(@NonNull @QCItemType String type) {
+            if (!isValidType(type)) {
+                throw new IllegalArgumentException("Invalid QCActionItem type provided" + type);
+            }
+            mType = type;
+        }
+
+        /**
+         * Sets whether or not the action item should be checked.
+         */
+        public Builder setChecked(boolean checked) {
+            mIsChecked = checked;
+            return this;
+        }
+
+        /**
+         * Sets whether or not the action item should be enabled.
+         */
+        public Builder setEnabled(boolean enabled) {
+            mIsEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether or not the action item is available.
+         */
+        public Builder setAvailable(boolean available) {
+            mIsAvailable = available;
+            return this;
+        }
+
+        /**
+         * Sets the icon for {@link QC_TYPE_ACTION_TOGGLE} actions
+         */
+        public Builder setIcon(@Nullable Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the PendingIntent to be sent when the action item is clicked.
+         */
+        public Builder setAction(@Nullable PendingIntent action) {
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Builds the final {@link QCActionItem}.
+         */
+        public QCActionItem build() {
+            return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable, mIcon, mAction);
+        }
+
+        private boolean isValidType(String type) {
+            return type.equals(QC_TYPE_ACTION_SWITCH) || type.equals(QC_TYPE_ACTION_TOGGLE);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/QCItem.java b/car-qc-lib/src/com/android/car/qc/QCItem.java
new file mode 100644
index 0000000..77fe30f
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCItem.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for all quick controls elements.
+ */
+public abstract class QCItem implements Parcelable {
+    public static final String QC_TYPE_LIST = "QC_TYPE_LIST";
+    public static final String QC_TYPE_ROW = "QC_TYPE_ROW";
+    public static final String QC_TYPE_TILE = "QC_TYPE_TILE";
+    public static final String QC_TYPE_SLIDER = "QC_TYPE_SLIDER";
+    public static final String QC_TYPE_ACTION_SWITCH = "QC_TYPE_ACTION_SWITCH";
+    public static final String QC_TYPE_ACTION_TOGGLE = "QC_TYPE_ACTION_TOGGLE";
+
+    public static final String QC_ACTION_TOGGLE_STATE = "QC_ACTION_TOGGLE_STATE";
+    public static final String QC_ACTION_SLIDER_VALUE = "QC_ACTION_SLIDER_VALUE";
+
+    @StringDef(value = {
+            QC_TYPE_LIST,
+            QC_TYPE_ROW,
+            QC_TYPE_TILE,
+            QC_TYPE_SLIDER,
+            QC_TYPE_ACTION_SWITCH,
+            QC_TYPE_ACTION_TOGGLE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface QCItemType {
+    }
+
+    private final String mType;
+    private ActionHandler mActionHandler;
+
+    public QCItem(@NonNull @QCItemType String type) {
+        mType = type;
+    }
+
+    public QCItem(@NonNull Parcel in) {
+        mType = in.readString();
+    }
+
+    @NonNull
+    @QCItemType
+    public String getType() {
+        return mType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mType);
+    }
+
+    public void setActionHandler(@Nullable ActionHandler handler) {
+        mActionHandler = handler;
+    }
+
+    @Nullable
+    public ActionHandler getActionHandler() {
+        return mActionHandler;
+    }
+
+    /**
+     * Returns the PendingIntent that is sent when the item is clicked.
+     */
+    @Nullable
+    public abstract PendingIntent getPrimaryAction();
+
+    /**
+     * Action handler that can listen for an action to occur and notify listeners.
+     */
+    public interface ActionHandler {
+        /**
+         * Callback when an action occurs.
+         * @param item the QCItem that sent the action
+         * @param context the context for the action
+         * @param intent the intent that was sent with the action
+         */
+        void onAction(@NonNull QCItem item, @NonNull Context context, @NonNull Intent intent);
+
+        default boolean isActivity() {
+            return false;
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/QCList.java b/car-qc-lib/src/com/android/car/qc/QCList.java
new file mode 100644
index 0000000..250b5d8
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCList.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.app.PendingIntent;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wrapping quick controls element that contains QCRow elements.
+ */
+public class QCList extends QCItem {
+    private final List<QCRow> mRows;
+
+    public QCList(@NonNull List<QCRow> rows) {
+        super(QC_TYPE_LIST);
+        mRows = Collections.unmodifiableList(rows);
+    }
+
+    public QCList(@NonNull Parcel in) {
+        super(in);
+        int rowCount = in.readInt();
+        List<QCRow> rows = new ArrayList<>();
+        for (int i = 0; i < rowCount; i++) {
+            rows.add(QCRow.CREATOR.createFromParcel(in));
+        }
+        mRows = Collections.unmodifiableList(rows);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mRows.size());
+        for (QCRow row : mRows) {
+            row.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public PendingIntent getPrimaryAction() {
+        return null;
+    }
+
+    @NonNull
+    public List<QCRow> getRows() {
+        return mRows;
+    }
+
+    public static Creator<QCList> CREATOR = new Creator<QCList>() {
+        @Override
+        public QCList createFromParcel(Parcel source) {
+            return new QCList(source);
+        }
+
+        @Override
+        public QCList[] newArray(int size) {
+            return new QCList[size];
+        }
+    };
+
+    /**
+     * Builder for {@link QCList}.
+     */
+    public static class Builder {
+        private final List<QCRow> mRows = new ArrayList<>();
+
+        /**
+         * Adds a {@link QCRow} to the list.
+         */
+        public Builder addRow(@NonNull QCRow row) {
+            mRows.add(row);
+            return this;
+        }
+
+        /**
+         * Builds the final {@link QCList}.
+         */
+        public QCList build() {
+            return new QCList(mRows);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/QCRow.java b/car-qc-lib/src/com/android/car/qc/QCRow.java
new file mode 100644
index 0000000..deb4429
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCRow.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Quick Control Row Element
+ * ------------------------------------
+ * |            | Title    |          |
+ * | StartItems | Subtitle | EndItems |
+ * |            | Sliders  |          |
+ * ------------------------------------
+ */
+public class QCRow extends QCItem {
+    private final String mTitle;
+    private final String mSubtitle;
+    private final Icon mStartIcon;
+    private final boolean mIsStartIconTintable;
+    private final QCSlider mSlider;
+    private final List<QCActionItem> mStartItems;
+    private final List<QCActionItem> mEndItems;
+    private final PendingIntent mPrimaryAction;
+
+    public QCRow(@Nullable String title, @Nullable String subtitle,
+            @Nullable PendingIntent primaryAction, @Nullable Icon startIcon, boolean isIconTintable,
+            @Nullable QCSlider slider, @NonNull List<QCActionItem> startItems,
+            @NonNull List<QCActionItem> endItems) {
+        super(QC_TYPE_ROW);
+        mTitle = title;
+        mSubtitle = subtitle;
+        mPrimaryAction = primaryAction;
+        mStartIcon = startIcon;
+        mIsStartIconTintable = isIconTintable;
+        mSlider = slider;
+        mStartItems = Collections.unmodifiableList(startItems);
+        mEndItems = Collections.unmodifiableList(endItems);
+    }
+
+    public QCRow(@NonNull Parcel in) {
+        super(in);
+        mTitle = in.readString();
+        mSubtitle = in.readString();
+        boolean hasIcon = in.readBoolean();
+        if (hasIcon) {
+            mStartIcon = Icon.CREATOR.createFromParcel(in);
+        } else {
+            mStartIcon = null;
+        }
+        mIsStartIconTintable = in.readBoolean();
+        boolean hasSlider = in.readBoolean();
+        if (hasSlider) {
+            mSlider = QCSlider.CREATOR.createFromParcel(in);
+        } else {
+            mSlider = null;
+        }
+        List<QCActionItem> startItems = new ArrayList<>();
+        int startItemCount = in.readInt();
+        for (int i = 0; i < startItemCount; i++) {
+            startItems.add(QCActionItem.CREATOR.createFromParcel(in));
+        }
+        mStartItems = Collections.unmodifiableList(startItems);
+        List<QCActionItem> endItems = new ArrayList<>();
+        int endItemCount = in.readInt();
+        for (int i = 0; i < endItemCount; i++) {
+            endItems.add(QCActionItem.CREATOR.createFromParcel(in));
+        }
+        mEndItems = Collections.unmodifiableList(endItems);
+        boolean hasPrimaryAction = in.readBoolean();
+        if (hasPrimaryAction) {
+            mPrimaryAction = PendingIntent.CREATOR.createFromParcel(in);
+        } else {
+            mPrimaryAction = null;
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mTitle);
+        dest.writeString(mSubtitle);
+        boolean hasStartIcon = mStartIcon != null;
+        dest.writeBoolean(hasStartIcon);
+        if (hasStartIcon) {
+            mStartIcon.writeToParcel(dest, flags);
+        }
+        dest.writeBoolean(mIsStartIconTintable);
+        boolean hasSlider = mSlider != null;
+        dest.writeBoolean(hasSlider);
+        if (hasSlider) {
+            mSlider.writeToParcel(dest, flags);
+        }
+        dest.writeInt(mStartItems.size());
+        for (QCActionItem startItem : mStartItems) {
+            startItem.writeToParcel(dest, flags);
+        }
+        dest.writeInt(mEndItems.size());
+        for (QCActionItem endItem : mEndItems) {
+            endItem.writeToParcel(dest, flags);
+        }
+        dest.writeBoolean(mPrimaryAction != null);
+        boolean hasPrimaryAction = mPrimaryAction != null;
+        if (hasPrimaryAction) {
+            mPrimaryAction.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public PendingIntent getPrimaryAction() {
+        return mPrimaryAction;
+    }
+
+    @Nullable
+    public String getTitle() {
+        return mTitle;
+    }
+
+    @Nullable
+    public String getSubtitle() {
+        return mSubtitle;
+    }
+
+    @Nullable
+    public Icon getStartIcon() {
+        return mStartIcon;
+    }
+
+    public boolean isStartIconTintable() {
+        return mIsStartIconTintable;
+    }
+
+    @Nullable
+    public QCSlider getSlider() {
+        return mSlider;
+    }
+
+    @NonNull
+    public List<QCActionItem> getStartItems() {
+        return mStartItems;
+    }
+
+    @NonNull
+    public List<QCActionItem> getEndItems() {
+        return mEndItems;
+    }
+
+    public static Creator<QCRow> CREATOR = new Creator<QCRow>() {
+        @Override
+        public QCRow createFromParcel(Parcel source) {
+            return new QCRow(source);
+        }
+
+        @Override
+        public QCRow[] newArray(int size) {
+            return new QCRow[size];
+        }
+    };
+
+    /**
+     * Builder for {@link QCRow}.
+     */
+    public static class Builder {
+        private final List<QCActionItem> mStartItems = new ArrayList<>();
+        private final List<QCActionItem> mEndItems = new ArrayList<>();
+        private Icon mStartIcon;
+        private boolean mIsStartIconTintable = true;
+        private String mTitle;
+        private String mSubtitle;
+        private QCSlider mSlider;
+        private PendingIntent mPrimaryAction;
+
+        /**
+         * Sets the row title.
+         */
+        public Builder setTitle(@Nullable String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the row subtitle.
+         */
+        public Builder setSubtitle(@Nullable String subtitle) {
+            mSubtitle = subtitle;
+            return this;
+        }
+
+        /**
+         * Sets the row icon.
+         */
+        public Builder setIcon(@Nullable Icon icon) {
+            mStartIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets whether or not the row icon is tintable.
+         */
+        public Builder setIconTintable(boolean tintable) {
+            mIsStartIconTintable = tintable;
+            return this;
+        }
+
+        /**
+         * Adds a {@link QCSlider} to the slider area.
+         */
+        public Builder addSlider(@Nullable QCSlider slider) {
+            mSlider = slider;
+            return this;
+        }
+
+        /**
+         * Sets the PendingIntent to be sent when the row is clicked.
+         */
+        public Builder setPrimaryAction(@Nullable PendingIntent action) {
+            mPrimaryAction = action;
+            return this;
+        }
+
+        /**
+         * Adds a {@link QCActionItem} to the start items area.
+         */
+        public Builder addStartItem(@NonNull QCActionItem item) {
+            mStartItems.add(item);
+            return this;
+        }
+
+        /**
+         * Adds a {@link QCActionItem} to the end items area.
+         */
+        public Builder addEndItem(@NonNull QCActionItem item) {
+            mEndItems.add(item);
+            return this;
+        }
+
+        /**
+         * Builds the final {@link QCRow}.
+         */
+        public QCRow build() {
+            return new QCRow(mTitle, mSubtitle, mPrimaryAction, mStartIcon, mIsStartIconTintable,
+                    mSlider, mStartItems, mEndItems);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/QCSlider.java b/car-qc-lib/src/com/android/car/qc/QCSlider.java
new file mode 100644
index 0000000..16be5e7
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCSlider.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.app.PendingIntent;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Quick Control Slider included in {@link QCRow}
+ */
+public class QCSlider extends QCItem {
+    private int mMin = 0;
+    private int mMax = 100;
+    private int mValue = 0;
+    private PendingIntent mInputAction;
+
+    public QCSlider(int min, int max, int value, @Nullable PendingIntent inputAction) {
+        super(QC_TYPE_SLIDER);
+        mMin = min;
+        mMax = max;
+        mValue = value;
+        mInputAction = inputAction;
+    }
+
+    public QCSlider(@NonNull Parcel in) {
+        super(in);
+        mMin = in.readInt();
+        mMax = in.readInt();
+        mValue = in.readInt();
+        boolean hasAction = in.readBoolean();
+        if (hasAction) {
+            mInputAction = PendingIntent.CREATOR.createFromParcel(in);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mMin);
+        dest.writeInt(mMax);
+        dest.writeInt(mValue);
+        boolean hasAction = mInputAction != null;
+        dest.writeBoolean(hasAction);
+        if (hasAction) {
+            mInputAction.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public PendingIntent getPrimaryAction() {
+        return mInputAction;
+    }
+
+    public int getMin() {
+        return mMin;
+    }
+
+    public int getMax() {
+        return mMax;
+    }
+
+    public int getValue() {
+        return mValue;
+    }
+
+    public static Creator<QCSlider> CREATOR = new Creator<QCSlider>() {
+        @Override
+        public QCSlider createFromParcel(Parcel source) {
+            return new QCSlider(source);
+        }
+
+        @Override
+        public QCSlider[] newArray(int size) {
+            return new QCSlider[size];
+        }
+    };
+
+    /**
+     * Builder for {@link QCSlider}.
+     */
+    public static class Builder {
+        private int mMin = 0;
+        private int mMax = 100;
+        private int mValue = 0;
+        private PendingIntent mInputAction;
+
+        /**
+         * Set the minimum allowed value for the slider input.
+         */
+        public Builder setMin(int min) {
+            mMin = min;
+            return this;
+        }
+
+        /**
+         * Set the maximum allowed value for the slider input.
+         */
+        public Builder setMax(int max) {
+            mMax = max;
+            return this;
+        }
+
+        /**
+         * Set the current value for the slider input.
+         */
+        public Builder setValue(int value) {
+            mValue = value;
+            return this;
+        }
+
+        /**
+         * Set the PendingIntent to be sent when the slider value is changed.
+         */
+        public Builder setInputAction(@Nullable PendingIntent inputAction) {
+            mInputAction = inputAction;
+            return this;
+        }
+
+        /**
+         * Builds the final {@link QCSlider}.
+         */
+        public QCSlider build() {
+            return new QCSlider(mMin, mMax, mValue, mInputAction);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/QCTile.java b/car-qc-lib/src/com/android/car/qc/QCTile.java
new file mode 100644
index 0000000..5f891e6
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/QCTile.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Quick Control Tile Element
+ * ------------
+ * | -------- |
+ * | | Icon | |
+ * | -------- |
+ * | Subtitle |
+ * ------------
+ */
+public class QCTile extends QCItem {
+    private final boolean mIsChecked;
+    private final boolean mIsEnabled;
+    private final boolean mIsAvailable;
+    private final String mSubtitle;
+    private Icon mIcon;
+    private PendingIntent mAction;
+
+    public QCTile(boolean isChecked, boolean isEnabled, boolean isAvailable,
+            @Nullable String subtitle, @Nullable Icon icon, @Nullable PendingIntent action) {
+        super(QC_TYPE_TILE);
+        mIsEnabled = isEnabled;
+        mIsChecked = isChecked;
+        mIsAvailable = isAvailable;
+        mSubtitle = subtitle;
+        mIcon = icon;
+        mAction = action;
+    }
+
+    public QCTile(@NonNull Parcel in) {
+        super(in);
+        mIsChecked = in.readBoolean();
+        mIsEnabled = in.readBoolean();
+        mIsAvailable = in.readBoolean();
+        mSubtitle = in.readString();
+        boolean hasIcon = in.readBoolean();
+        if (hasIcon) {
+            mIcon = Icon.CREATOR.createFromParcel(in);
+        }
+        boolean hasAction = in.readBoolean();
+        if (hasAction) {
+            mAction = PendingIntent.CREATOR.createFromParcel(in);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeBoolean(mIsChecked);
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mIsAvailable);
+        dest.writeString(mSubtitle);
+        boolean hasIcon = mIcon != null;
+        dest.writeBoolean(hasIcon);
+        if (hasIcon) {
+            mIcon.writeToParcel(dest, flags);
+        }
+        boolean hasAction = mAction != null;
+        dest.writeBoolean(hasAction);
+        if (hasAction) {
+            mAction.writeToParcel(dest, flags);
+        }
+    }
+
+    @Override
+    public PendingIntent getPrimaryAction() {
+        return mAction;
+    }
+
+    public boolean isChecked() {
+        return mIsChecked;
+    }
+
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    public boolean isAvailable() {
+        return mIsAvailable;
+    }
+
+    @Nullable
+    public String getSubtitle() {
+        return mSubtitle;
+    }
+
+    @Nullable
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    public static Creator<QCTile> CREATOR = new Creator<QCTile>() {
+        @Override
+        public QCTile createFromParcel(Parcel source) {
+            return new QCTile(source);
+        }
+
+        @Override
+        public QCTile[] newArray(int size) {
+            return new QCTile[size];
+        }
+    };
+
+    /**
+     * Builder for {@link QCTile}.
+     */
+    public static class Builder {
+        private boolean mIsChecked;
+        private boolean mIsEnabled = true;
+        private boolean mIsAvailable = true;
+        private String mSubtitle;
+        private Icon mIcon;
+        private PendingIntent mAction;
+
+        /**
+         * Sets whether or not the tile should be checked.
+         */
+        public Builder setChecked(boolean checked) {
+            mIsChecked = checked;
+            return this;
+        }
+
+        /**
+         * Sets whether or not the tile should be enabled.
+         */
+        public Builder setEnabled(boolean enabled) {
+            mIsEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether or not the action item is available.
+         */
+        public Builder setAvailable(boolean available) {
+            mIsAvailable = available;
+            return this;
+        }
+
+        /**
+         * Sets the tile's subtitle.
+         */
+        public Builder setSubtitle(@Nullable String subtitle) {
+            mSubtitle = subtitle;
+            return this;
+        }
+
+        /**
+         * Sets the tile's icon.
+         */
+        public Builder setIcon(@Nullable Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the PendingIntent to be sent when the tile is clicked.
+         */
+        public Builder setAction(@Nullable PendingIntent action) {
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Builds the final {@link QCTile}.
+         */
+        public QCTile build() {
+            return new QCTile(mIsChecked, mIsEnabled, mIsAvailable, mSubtitle, mIcon, mAction);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/controller/BaseQCController.java b/car-qc-lib/src/com/android/car/qc/controller/BaseQCController.java
new file mode 100644
index 0000000..ce2bea3
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/controller/BaseQCController.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.lifecycle.Observer;
+
+import com.android.car.qc.QCItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base controller class for Quick Controls.
+ */
+public abstract class BaseQCController implements QCItemCallback {
+    protected final Context mContext;
+    protected final List<Observer<QCItem>> mObservers = new ArrayList<>();
+    protected boolean mShouldListen = false;
+    protected boolean mWasListening = false;
+    protected QCItem mQCItem;
+
+    public BaseQCController(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Update whether or not the controller should be listening to updates from the provider.
+     */
+    public void listen(boolean shouldListen) {
+        mShouldListen = shouldListen;
+        updateListening();
+    }
+
+    /**
+     * Add a QCItem observer to the controller.
+     */
+    @UiThread
+    public void addObserver(Observer<QCItem> observer) {
+        mObservers.add(observer);
+        updateListening();
+    }
+
+    /**
+     * Remove a QCItem observer from the controller.
+     */
+    @UiThread
+    public void removeObserver(Observer<QCItem> observer) {
+        mObservers.remove(observer);
+        updateListening();
+    }
+
+    @UiThread
+    @Override
+    public void onQCItemUpdated(@Nullable QCItem item) {
+        mQCItem = item;
+        mObservers.forEach(o -> o.onChanged(mQCItem));
+    }
+
+    /**
+     * Destroy the controller. This should be called when the controller is no longer needed so
+     * the listeners can be cleaned up.
+     */
+    public void destroy() {
+        mShouldListen = false;
+        mObservers.clear();
+        updateListening();
+    }
+
+    /**
+     * Subclasses must override this method to handle a listening update.
+     */
+    protected abstract void updateListening();
+}
diff --git a/car-qc-lib/src/com/android/car/qc/controller/LocalQCController.java b/car-qc-lib/src/com/android/car/qc/controller/LocalQCController.java
new file mode 100644
index 0000000..01bd6f8
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/controller/LocalQCController.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import android.content.Context;
+
+import com.android.car.qc.provider.BaseLocalQCProvider;
+
+/**
+ * Controller for binding to local quick control providers.
+ */
+public class LocalQCController extends BaseQCController {
+
+    private final BaseLocalQCProvider mProvider;
+
+    private final BaseLocalQCProvider.Notifier mProviderNotifier =
+            new BaseLocalQCProvider.Notifier() {
+                @Override
+                public void notifyUpdate() {
+                    if (mShouldListen && !mObservers.isEmpty()) {
+                        onQCItemUpdated(mProvider.getQCItem());
+                    }
+                }
+            };
+
+    public LocalQCController(Context context, BaseLocalQCProvider provider) {
+        super(context);
+        mProvider = provider;
+        mProvider.setNotifier(mProviderNotifier);
+        mQCItem = mProvider.getQCItem();
+    }
+
+    @Override
+    protected void updateListening() {
+        boolean listen = mShouldListen && !mObservers.isEmpty();
+        if (mWasListening != listen) {
+            mWasListening = listen;
+            mProvider.shouldListen(listen);
+            if (listen) {
+                mQCItem = mProvider.getQCItem();
+                onQCItemUpdated(mQCItem);
+            }
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        mProvider.onDestroy();
+    }
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java b/car-qc-lib/src/com/android/car/qc/controller/QCItemCallback.java
similarity index 64%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java
rename to car-qc-lib/src/com/android/car/qc/controller/QCItemCallback.java
index a6e10f7..b5efdef 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java
+++ b/car-qc-lib/src/com/android/car/qc/controller/QCItemCallback.java
@@ -13,13 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+
+package com.android.car.qc.controller;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.qc.QCItem;
 
 /**
- * {@link androidx.recyclerview.widget.GridLayoutManager#setSpanSizeLookup}
+ * Callback to be executed when a QCItem changes.
  */
-public interface SpanSizeLookupOEMV1 {
-
-    /** {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup#getSpanSize} */
-    int getSpanSize(int position);
+public interface QCItemCallback {
+    /**
+     * Called when QCItem is updated.
+     *
+     * @param item The updated QCItem.
+     */
+    void onQCItemUpdated(@Nullable QCItem item);
 }
diff --git a/car-qc-lib/src/com/android/car/qc/controller/RemoteQCController.java b/car-qc-lib/src/com/android/car/qc/controller/RemoteQCController.java
new file mode 100644
index 0000000..c98ca86
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/controller/RemoteQCController.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import static com.android.car.qc.provider.BaseQCProvider.EXTRA_ITEM;
+import static com.android.car.qc.provider.BaseQCProvider.EXTRA_URI;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_BIND;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_DESTROY;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_SUBSCRIBE;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_UNSUBSCRIBE;
+
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.car.qc.QCItem;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controller for binding to remote quick control providers.
+ */
+public class RemoteQCController extends BaseQCController {
+    private static final String TAG = "RemoteQCController";
+    private static final long PROVIDER_ANR_TIMEOUT = 3000L;
+
+    private final Uri mUri;
+    private final Executor mBackgroundExecutor;
+    private final HandlerThread mBackgroundHandlerThread;
+    private final ArrayMap<Pair<Uri, QCItemCallback>, QCObserver> mObserverLookup =
+            new ArrayMap<>();
+
+    public RemoteQCController(Context context, Uri uri) {
+        super(context);
+        mUri = uri;
+        mBackgroundHandlerThread = new HandlerThread(/* name= */ TAG + "HandlerThread");
+        mBackgroundHandlerThread.start();
+        mBackgroundExecutor = new HandlerExecutor(
+                new Handler(mBackgroundHandlerThread.getLooper()));
+    }
+
+    @VisibleForTesting
+    RemoteQCController(Context context, Uri uri, Executor backgroundExecutor) {
+        super(context);
+        mUri = uri;
+        mBackgroundHandlerThread = null;
+        mBackgroundExecutor = backgroundExecutor;
+    }
+
+    @Override
+    protected void updateListening() {
+        boolean listen = mShouldListen && !mObservers.isEmpty();
+        mBackgroundExecutor.execute(() -> updateListeningBg(listen));
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        if (mBackgroundHandlerThread != null) {
+            mBackgroundHandlerThread.quit();
+        }
+        try (ContentProviderClient client = getClient()) {
+            if (client == null) {
+                return;
+            }
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_URI, mUri);
+            try {
+                client.call(METHOD_DESTROY, /* arg= */ null, b);
+            } catch (Exception e) {
+                Log.d(TAG, "Error destroying QCItem", e);
+            }
+        }
+    }
+
+    @WorkerThread
+    private void updateListeningBg(boolean isListening) {
+        if (mWasListening != isListening) {
+            mWasListening = isListening;
+            if (isListening) {
+                registerQCCallback(mContext.getMainExecutor(), /* callback= */ this);
+                // Update one-time on a different thread so that it can display in parallel
+                mBackgroundExecutor.execute(this::updateQCItem);
+            } else {
+                unregisterQCCallback(this);
+            }
+        }
+    }
+
+    @WorkerThread
+    private void updateQCItem() {
+        try {
+            QCItem item = bind();
+            mContext.getMainExecutor().execute(() -> onQCItemUpdated(item));
+        } catch (Exception e) {
+            Log.d(TAG, "Error fetching QCItem", e);
+        }
+    }
+
+    private QCItem bind() {
+        try (ContentProviderClient provider = getClient()) {
+            if (provider == null) {
+                return null;
+            }
+            Bundle extras = new Bundle();
+            extras.putParcelable(EXTRA_URI, mUri);
+            Bundle res = provider.call(METHOD_BIND, /* arg= */ null, extras);
+            if (res == null) {
+                return null;
+            }
+            res.setDefusable(true);
+            res.setClassLoader(QCItem.class.getClassLoader());
+            Parcelable parcelable = res.getParcelable(EXTRA_ITEM);
+            if (parcelable instanceof QCItem) {
+                return (QCItem) parcelable;
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error binding QCItem", e);
+            return null;
+        }
+    }
+
+    private void subscribe() {
+        try (ContentProviderClient client = getClient()) {
+            if (client == null) {
+                return;
+            }
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_URI, mUri);
+            try {
+                client.call(METHOD_SUBSCRIBE, /* arg= */ null, b);
+            } catch (Exception e) {
+                Log.d(TAG, "Error subscribing to QCItem", e);
+            }
+        }
+    }
+
+    private void unsubscribe()  {
+        try (ContentProviderClient client = getClient()) {
+            if (client == null) {
+                return;
+            }
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_URI, mUri);
+            try {
+                client.call(METHOD_UNSUBSCRIBE, /* arg= */ null, b);
+            } catch (Exception e) {
+                Log.d(TAG, "Error unsubscribing from QCItem", e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    ContentProviderClient getClient() {
+        ContentProviderClient client = mContext.getContentResolver()
+                .acquireContentProviderClient(mUri);
+        if (client == null) {
+            return null;
+        }
+        client.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+        return client;
+    }
+
+    private void registerQCCallback(@NonNull Executor executor, @NonNull QCItemCallback callback) {
+        getObserver(callback, new QCObserver(mUri, executor, callback)).startObserving();
+    }
+
+    private void unregisterQCCallback(@NonNull QCItemCallback callback) {
+        synchronized (mObserverLookup) {
+            QCObserver observer = mObserverLookup.remove(new Pair<>(mUri, callback));
+            if (observer != null) {
+                observer.stopObserving();
+            }
+        }
+    }
+
+    private QCObserver getObserver(QCItemCallback callback, QCObserver observer) {
+        Pair<Uri, QCItemCallback> key = new Pair<>(mUri, callback);
+        synchronized (mObserverLookup) {
+            QCObserver oldObserver = mObserverLookup.put(key, observer);
+            if (oldObserver != null) {
+                oldObserver.stopObserving();
+            }
+        }
+        return observer;
+    }
+
+    private class QCObserver {
+        private final Uri mUri;
+        private final Executor mExecutor;
+        private final QCItemCallback mCallback;
+        private boolean mIsSubscribed;
+
+        private final Runnable mUpdateItem = new Runnable() {
+            @Override
+            public void run() {
+                trySubscribe();
+                QCItem item = bind();
+                mExecutor.execute(() -> mCallback.onQCItemUpdated(item));
+            }
+        };
+
+        private final ContentObserver mObserver = new ContentObserver(
+                new Handler(Looper.getMainLooper())) {
+            @Override
+            public void onChange(boolean selfChange) {
+                android.os.AsyncTask.execute(mUpdateItem);
+            }
+        };
+
+        QCObserver(Uri uri, Executor executor, QCItemCallback callback) {
+            mUri = uri;
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        void startObserving() {
+            ContentProviderClient provider =
+                    mContext.getContentResolver().acquireContentProviderClient(mUri);
+            if (provider != null) {
+                provider.close();
+                mContext.getContentResolver().registerContentObserver(
+                        mUri, /* notifyForDescendants= */ true, mObserver);
+                trySubscribe();
+            }
+        }
+
+        void trySubscribe() {
+            if (!mIsSubscribed) {
+                subscribe();
+                mIsSubscribed = true;
+            }
+        }
+
+        void stopObserving() {
+            mContext.getContentResolver().unregisterContentObserver(mObserver);
+            if (mIsSubscribed) {
+                unsubscribe();
+                mIsSubscribed = false;
+            }
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/provider/BaseLocalQCProvider.java b/car-qc-lib/src/com/android/car/qc/provider/BaseLocalQCProvider.java
new file mode 100644
index 0000000..764d2a3
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/provider/BaseLocalQCProvider.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 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.qc.provider;
+
+import android.content.Context;
+
+import com.android.car.qc.QCItem;
+
+/**
+ * Base class for local Quick Control providers.
+ */
+public abstract class BaseLocalQCProvider {
+
+    /**
+     * Callback to be executed when the QCItem updates.
+     */
+    public interface Notifier {
+        /**
+         * Called when the QCItem has been updated.
+         */
+        default void notifyUpdate() {
+        }
+    }
+
+    private Notifier mNotifier;
+    private boolean mIsListening;
+    protected final Context mContext;
+
+    public BaseLocalQCProvider(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Set the notifier that should be called when the QCItem updates.
+     */
+    public void setNotifier(Notifier notifier) {
+        mNotifier = notifier;
+    }
+
+    /**
+     * Update whether or not the provider should be listening for live updates.
+     */
+    public void shouldListen(boolean listen) {
+        if (mIsListening == listen) {
+            return;
+        }
+        mIsListening = listen;
+        if (listen) {
+            onSubscribed();
+        } else {
+            onUnsubscribed();
+        }
+    }
+
+    /**
+     * Method to create and return a {@link QCItem}.
+     */
+    public abstract QCItem getQCItem();
+
+    /**
+     * Called to inform the provider that it has been subscribed to.
+     */
+    protected void onSubscribed() {
+    }
+
+    /**
+     * Called to inform the provider that it has been unsubscribed from.
+     */
+    protected void onUnsubscribed() {
+    }
+
+    /**
+     * Called to inform the provider that it is being destroyed.
+     */
+    public void onDestroy() {
+    }
+
+    protected void notifyChange() {
+        if (mNotifier != null) {
+            mNotifier.notifyUpdate();
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
new file mode 100644
index 0000000..61db361
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2021 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.qc.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.StrictMode;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.qc.QCItem;
+
+import java.util.Set;
+
+/**
+ * Base Quick Controls provider implementation.
+ */
+public abstract class BaseQCProvider extends ContentProvider {
+    public static final String METHOD_BIND = "QC_METHOD_BIND";
+    public static final String METHOD_SUBSCRIBE = "QC_METHOD_SUBSCRIBE";
+    public static final String METHOD_UNSUBSCRIBE = "QC_METHOD_UNSUBSCRIBE";
+    public static final String METHOD_DESTROY = "QC_METHOD_DESTROY";
+    public static final String EXTRA_URI = "QC_EXTRA_URI";
+    public static final String EXTRA_ITEM = "QC_EXTRA_ITEM";
+
+    private static final String TAG = "BaseQCProvider";
+    private static final long QC_ANR_TIMEOUT = 3000L;
+    private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+    private String mCallbackMethod;
+    private final Runnable mAnr = () -> {
+        Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT);
+        Log.e(TAG, "Timed out while handling QC method " + mCallbackMethod);
+    };
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        enforceCallingPermissions();
+
+        Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
+                extras.getParcelable(EXTRA_URI)));
+        switch(method) {
+            case METHOD_BIND:
+                QCItem item = handleBind(uri);
+                Bundle b = new Bundle();
+                b.putParcelable(EXTRA_ITEM, item);
+                return b;
+            case METHOD_SUBSCRIBE:
+                handleSubscribe(uri);
+                break;
+            case METHOD_UNSUBSCRIBE:
+                handleUnsubscribe(uri);
+                break;
+            case METHOD_DESTROY:
+                handleDestroy(uri);
+                break;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    /**
+     * Method to create and return a {@link QCItem}.
+     *
+     * onBind is expected to return as quickly as possible. Therefore, no network or other IO
+     * will be allowed. Any loading that needs to be done should happen in the background and
+     * should then notify the content resolver of the change when ready to provide the
+     * complete data in onBind.
+     */
+    @Nullable
+    protected QCItem onBind(@NonNull Uri uri) {
+        return null;
+    }
+
+    /**
+     * Called to inform an app that an item has been subscribed to.
+     *
+     * Subscribing is a way that a host can notify apps of which QCItems they would like to
+     * receive updates for. The providing apps are expected to keep the content up to date
+     * and notify of change via the content resolver.
+     */
+    protected void onSubscribed(@NonNull Uri uri) {
+    }
+
+    /**
+     * Called to inform an app that an item has been unsubscribed from.
+     *
+     * This is used to notify providing apps that a host is no longer listening
+     * to updates, so any background processes and/or listeners should be removed.
+     */
+    protected void onUnsubscribed(@NonNull Uri uri) {
+    }
+
+    /**
+     * Called to inform an app that an item is being destroyed.
+     *
+     * This is used to notify providing apps that a host is no longer going to use this QCItem
+     * instance, so the relevant elements should be cleaned up.
+     */
+    protected void onDestroy(@NonNull Uri uri) {
+    }
+
+    /**
+     * Returns a Set of packages that are allowed to call this provider.
+     */
+    @NonNull
+    protected abstract Set<String> getAllowlistedPackages();
+
+    private QCItem handleBind(Uri uri) {
+        mCallbackMethod = "handleBind";
+        MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT);
+        try {
+            return onBindStrict(uri);
+        } finally {
+            MAIN_THREAD_HANDLER.removeCallbacks(mAnr);
+        }
+    }
+
+    private QCItem onBindStrict(@NonNull Uri uri) {
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        try {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectAll()
+                    .penaltyDeath()
+                    .build());
+            return onBind(uri);
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    private void handleSubscribe(@NonNull Uri uri) {
+        mCallbackMethod = "handleSubscribe";
+        MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT);
+        try {
+            onSubscribed(uri);
+        } finally {
+            MAIN_THREAD_HANDLER.removeCallbacks(mAnr);
+        }
+    }
+
+    private void handleUnsubscribe(@NonNull Uri uri) {
+        mCallbackMethod = "handleUnsubscribe";
+        MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT);
+        try {
+            onUnsubscribed(uri);
+        } finally {
+            MAIN_THREAD_HANDLER.removeCallbacks(mAnr);
+        }
+    }
+
+    private void handleDestroy(@NonNull Uri uri) {
+        mCallbackMethod = "handleDestroy";
+        MAIN_THREAD_HANDLER.postDelayed(mAnr, QC_ANR_TIMEOUT);
+        try {
+            onDestroy(uri);
+        } finally {
+            MAIN_THREAD_HANDLER.removeCallbacks(mAnr);
+        }
+    }
+
+    private Uri validateIncomingUriOrNull(Uri uri) {
+        if (uri == null) {
+            throw new IllegalArgumentException("Uri cannot be null");
+        }
+        return validateIncomingUri(uri);
+    }
+
+    private void enforceCallingPermissions() {
+        String callingPackage = getCallingPackage();
+        if (callingPackage == null) {
+            throw new IllegalArgumentException("Calling package cannot be null");
+        }
+        if (!getAllowlistedPackages().contains(callingPackage)) {
+            throw new SecurityException(
+                    String.format("%s is not permitted to access provider: %s", callingPackage,
+                            getClass().getName()));
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCListView.java b/car-qc-lib/src/com/android/car/qc/view/QCListView.java
new file mode 100644
index 0000000..9aba976
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCListView.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.android.car.qc.view.QCView.QCActionListener;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.lifecycle.Observer;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCList;
+
+/**
+ * Quick Controls view for {@link QCList} instances.
+ */
+public class QCListView extends LinearLayout implements Observer<QCItem> {
+
+    private QCActionListener mActionListener;
+
+    public QCListView(Context context) {
+        super(context);
+        init();
+    }
+
+    public QCListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public QCListView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public QCListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        setOrientation(VERTICAL);
+    }
+
+    /**
+     * Set the view's {@link QCActionListener}. This listener will propagate to all QCRows.
+     */
+    public void setActionListener(QCActionListener listener) {
+        mActionListener = listener;
+        for (int i = 0; i < getChildCount(); i++) {
+            QCRowView view = (QCRowView) getChildAt(i);
+            view.setActionListener(mActionListener);
+        }
+    }
+
+    @Override
+    public void onChanged(QCItem qcItem) {
+        if (qcItem == null) {
+            removeAllViews();
+            return;
+        }
+        if (!qcItem.getType().equals(QCItem.QC_TYPE_LIST)) {
+            throw new IllegalArgumentException("Expected QCList type for QCListView but got "
+                    + qcItem.getType());
+        }
+        QCList qcList = (QCList) qcItem;
+        int rowCount = qcList.getRows().size();
+        for (int i = 0; i < rowCount; i++) {
+            if (getChildAt(i) != null) {
+                QCRowView view = (QCRowView) getChildAt(i);
+                view.setRow(qcList.getRows().get(i));
+                view.setActionListener(mActionListener);
+            } else {
+                QCRowView view = new QCRowView(getContext());
+                view.setRow(qcList.getRows().get(i));
+                view.setActionListener(mActionListener);
+                addView(view);
+            }
+        }
+        if (getChildCount() > rowCount) {
+            // remove extra rows
+            removeViews(rowCount, getChildCount() - rowCount);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
new file mode 100644
index 0000000..14d4b19
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.android.car.qc.QCItem.QC_ACTION_SLIDER_VALUE;
+import static com.android.car.qc.QCItem.QC_ACTION_TOGGLE_STATE;
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
+import static com.android.car.qc.view.QCView.QCActionListener;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.qc.QCActionItem;
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCRow;
+import com.android.car.qc.QCSlider;
+import com.android.car.qc.R;
+import com.android.car.ui.utils.DirectManipulationHelper;
+import com.android.car.ui.uxr.DrawableStateToggleButton;
+
+/**
+ * Quick Controls view for {@link QCRow} instances.
+ */
+public class QCRowView extends FrameLayout {
+    private static final String TAG = "QCRowView";
+
+    private LayoutInflater mLayoutInflater;
+    private BidiFormatter mBidiFormatter;
+    private View mContentView;
+    private TextView mTitle;
+    private TextView mSubtitle;
+    private ImageView mStartIcon;
+    @ColorInt
+    private int mStartIconTint;
+    private LinearLayout mStartItemsContainer;
+    private LinearLayout mEndItemsContainer;
+    private LinearLayout mSeekBarContainer;
+    private SeekBar mSeekBar;
+    private QCActionListener mActionListener;
+    private boolean mInDirectManipulationMode;
+
+    private QCSeekbarChangeListener mSeekbarChangeListener;
+    private final View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (mSeekBar == null) {
+                return false;
+            }
+            // Consume nudge events in direct manipulation mode.
+            if (mInDirectManipulationMode
+                    && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                    || keyCode == KeyEvent.KEYCODE_DPAD_UP
+                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN)) {
+                return true;
+            }
+
+            // Handle events to enter or exit direct manipulation mode.
+            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    setInDirectManipulationMode(v, mSeekBar, !mInDirectManipulationMode);
+                }
+                return true;
+            }
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                if (mInDirectManipulationMode) {
+                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                        setInDirectManipulationMode(v, mSeekBar, false);
+                    }
+                    return true;
+                }
+            }
+
+            // Don't propagate confirm keys to the SeekBar to prevent a ripple effect on the thumb.
+            if (KeyEvent.isConfirmKey(keyCode)) {
+                return false;
+            }
+
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                return mSeekBar.onKeyDown(keyCode, event);
+            } else {
+                return mSeekBar.onKeyUp(keyCode, event);
+            }
+        }
+    };
+
+    private final View.OnFocusChangeListener mSeekBarFocusChangeListener =
+            (v, hasFocus) -> {
+                if (!hasFocus && mInDirectManipulationMode && mSeekBar != null) {
+                    setInDirectManipulationMode(v, mSeekBar, false);
+                }
+            };
+
+    private final View.OnGenericMotionListener mSeekBarScrollListener =
+            (v, event) -> {
+                if (!mInDirectManipulationMode || mSeekBar == null) {
+                    return false;
+                }
+                int adjustment = Math.round(event.getAxisValue(MotionEvent.AXIS_SCROLL));
+                if (adjustment == 0) {
+                    return false;
+                }
+                int count = Math.abs(adjustment);
+                int keyCode =
+                        adjustment < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT;
+                KeyEvent downEvent = new KeyEvent(event.getDownTime(), event.getEventTime(),
+                        KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0);
+                KeyEvent upEvent = new KeyEvent(event.getDownTime(), event.getEventTime(),
+                        KeyEvent.ACTION_UP, keyCode, /* repeat= */ 0);
+                for (int i = 0; i < count; i++) {
+                    mSeekBar.onKeyDown(keyCode, downEvent);
+                    mSeekBar.onKeyUp(keyCode, upEvent);
+                }
+                return true;
+            };
+
+    QCRowView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    QCRowView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    QCRowView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context);
+    }
+
+    QCRowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mLayoutInflater = LayoutInflater.from(context);
+        mBidiFormatter = BidiFormatter.getInstance();
+        mLayoutInflater.inflate(R.layout.qc_row_view, /* root= */ this);
+        mContentView = findViewById(R.id.qc_row_content);
+        mTitle = findViewById(R.id.qc_title);
+        mSubtitle = findViewById(R.id.qc_summary);
+        mStartIcon = findViewById(R.id.qc_icon);
+        mStartItemsContainer = findViewById(R.id.qc_row_start_items);
+        mEndItemsContainer = findViewById(R.id.qc_row_end_items);
+        mSeekBarContainer = findViewById(R.id.qc_seekbar_wrapper);
+        mSeekBar = findViewById(R.id.seekbar);
+    }
+
+    void setActionListener(QCActionListener listener) {
+        mActionListener = listener;
+    }
+
+    void setRow(QCRow row) {
+        if (row == null) {
+            setVisibility(GONE);
+            return;
+        }
+        setVisibility(VISIBLE);
+        if (row.getPrimaryAction() != null || row.getActionHandler() != null) {
+            mContentView.setOnClickListener(v -> {
+                fireAction(row, /* intent= */ null);
+            });
+        }
+        if (!TextUtils.isEmpty(row.getTitle())) {
+            mTitle.setVisibility(VISIBLE);
+            mTitle.setText(
+                    mBidiFormatter.unicodeWrap(row.getTitle(), TextDirectionHeuristics.LOCALE));
+        } else {
+            mTitle.setVisibility(GONE);
+        }
+        if (!TextUtils.isEmpty(row.getSubtitle())) {
+            mSubtitle.setVisibility(VISIBLE);
+            mSubtitle.setText(
+                    mBidiFormatter.unicodeWrap(row.getSubtitle(), TextDirectionHeuristics.LOCALE));
+        } else {
+            mSubtitle.setVisibility(GONE);
+        }
+        if (row.getStartIcon() != null) {
+            mStartIcon.setVisibility(VISIBLE);
+            Drawable drawable = row.getStartIcon().loadDrawable(getContext());
+            if (drawable != null && row.isStartIconTintable()) {
+                if (mStartIconTint == 0) {
+                    mStartIconTint = getContext().getColor(R.color.qc_start_icon_color);
+                }
+                drawable.setTint(mStartIconTint);
+            }
+            mStartIcon.setImageDrawable(drawable);
+        } else {
+            mStartIcon.setImageDrawable(null);
+            mStartIcon.setVisibility(GONE);
+        }
+        QCSlider slider = row.getSlider();
+        if (slider != null) {
+            mSeekBarContainer.setVisibility(View.VISIBLE);
+            initSlider(slider);
+        } else {
+            mSeekBarContainer.setVisibility(View.GONE);
+        }
+
+        int startItemCount = row.getStartItems().size();
+        for (int i = 0; i < startItemCount; i++) {
+            QCActionItem action = row.getStartItems().get(i);
+            initActionItem(mStartItemsContainer, mStartItemsContainer.getChildAt(i), action);
+        }
+        if (mStartItemsContainer.getChildCount() > startItemCount) {
+            // remove extra items
+            mStartItemsContainer.removeViews(startItemCount,
+                    mStartItemsContainer.getChildCount() - startItemCount);
+        }
+        if (startItemCount == 0) {
+            mStartItemsContainer.setVisibility(View.GONE);
+        } else {
+            mStartItemsContainer.setVisibility(View.VISIBLE);
+        }
+
+        int endItemCount = row.getEndItems().size();
+        for (int i = 0; i < endItemCount; i++) {
+            QCActionItem action = row.getEndItems().get(i);
+            initActionItem(mEndItemsContainer, mEndItemsContainer.getChildAt(i), action);
+        }
+        if (mEndItemsContainer.getChildCount() > endItemCount) {
+            // remove extra items
+            mEndItemsContainer.removeViews(endItemCount,
+                    mEndItemsContainer.getChildCount() - endItemCount);
+        }
+        if (endItemCount == 0) {
+            mEndItemsContainer.setVisibility(View.GONE);
+        } else {
+            mEndItemsContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void initActionItem(@NonNull ViewGroup root, @Nullable View actionView,
+            @NonNull QCActionItem action) {
+        if (action.getType().equals(QC_TYPE_ACTION_SWITCH)) {
+            initSwitchView(action, root, actionView);
+        } else {
+            initToggleView(action, root, actionView);
+        }
+    }
+
+    private void initSwitchView(QCActionItem action, ViewGroup root, View actionView) {
+        Switch switchView = actionView == null ? null : actionView.findViewById(
+                android.R.id.switch_widget);
+        if (switchView == null) {
+            actionView = createActionView(root, actionView, R.layout.qc_action_switch);
+            switchView = actionView.requireViewById(android.R.id.switch_widget);
+        }
+
+        switchView.setOnCheckedChangeListener(null);
+        switchView.setEnabled(action.isEnabled());
+        switchView.setChecked(action.isChecked());
+        switchView.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    Intent intent = new Intent();
+                    intent.putExtra(QC_ACTION_TOGGLE_STATE, isChecked);
+                    fireAction(action, intent);
+                });
+    }
+
+    private void initToggleView(QCActionItem action, ViewGroup root, View actionView) {
+        DrawableStateToggleButton toggleButton =
+                actionView == null ? null : actionView.findViewById(R.id.qc_toggle_button);
+        if (toggleButton == null) {
+            actionView = createActionView(root, actionView, R.layout.qc_action_toggle);
+            toggleButton = actionView.requireViewById(R.id.qc_toggle_button);
+        }
+        toggleButton.setText(null);
+        toggleButton.setTextOn(null);
+        toggleButton.setTextOff(null);
+        toggleButton.setOnCheckedChangeListener(null);
+        Drawable icon = QCViewUtils.getInstance(mContext).getToggleIcon(
+                action.getIcon(), action.isAvailable());
+        toggleButton.setButtonDrawable(icon);
+        toggleButton.setChecked(action.isChecked());
+        toggleButton.setEnabled(action.isEnabled() && action.isAvailable());
+        toggleButton.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    Intent intent = new Intent();
+                    intent.putExtra(QC_ACTION_TOGGLE_STATE, isChecked);
+                    fireAction(action, intent);
+                });
+    }
+
+    @NonNull
+    private View createActionView(@NonNull ViewGroup root, @Nullable View actionView,
+            @LayoutRes int resId) {
+        if (actionView != null) {
+            // remove current action view
+            root.removeView(actionView);
+        }
+        actionView = mLayoutInflater.inflate(resId, /* root = */ null);
+        root.addView(actionView);
+        return actionView;
+    }
+
+    private void initSlider(QCSlider slider) {
+        mSeekBar.setOnSeekBarChangeListener(null);
+        mSeekBar.setMin(slider.getMin());
+        mSeekBar.setMax(slider.getMax());
+        mSeekBar.setProgress(slider.getValue());
+        if (mSeekbarChangeListener == null) {
+            mSeekbarChangeListener = new QCSeekbarChangeListener();
+        }
+        mSeekbarChangeListener.setSlider(slider);
+        mSeekBar.setOnSeekBarChangeListener(mSeekbarChangeListener);
+        // set up rotary support
+        mSeekBarContainer.setOnKeyListener(mSeekBarKeyListener);
+        mSeekBarContainer.setOnFocusChangeListener(mSeekBarFocusChangeListener);
+        mSeekBarContainer.setOnGenericMotionListener(mSeekBarScrollListener);
+    }
+
+    private void setInDirectManipulationMode(View view, SeekBar seekbar, boolean enable) {
+        mInDirectManipulationMode = enable;
+        DirectManipulationHelper.enableDirectManipulationMode(seekbar, enable);
+        view.setSelected(enable);
+        seekbar.setSelected(enable);
+    }
+
+    private void fireAction(QCItem item, Intent intent) {
+        if (item.getPrimaryAction() != null) {
+            try {
+                item.getPrimaryAction().send(getContext(), 0, intent);
+                if (mActionListener != null) {
+                    mActionListener.onQCAction(item);
+                }
+            } catch (PendingIntent.CanceledException e) {
+                Log.d(TAG, "Error sending intent", e);
+            }
+        } else if (item.getActionHandler() != null) {
+            item.getActionHandler().onAction(item, getContext(), intent);
+            if (mActionListener != null) {
+                mActionListener.onQCAction(item);
+            }
+        }
+    }
+
+    private class QCSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+        // Interval of updates (in ms) sent in response to seekbar moving.
+        private static final int SLIDER_UPDATE_INTERVAL = 200;
+
+        private final Handler mSliderUpdateHandler;
+        private QCSlider mSlider;
+        private int mCurrSliderValue;
+        private boolean mSliderUpdaterRunning;
+        private long mLastSentSliderUpdate;
+        private final Runnable mSliderUpdater = () -> {
+            sendSliderValue();
+            mSliderUpdaterRunning = false;
+        };
+
+        QCSeekbarChangeListener() {
+            mSliderUpdateHandler = new Handler();
+        }
+
+        void setSlider(QCSlider slider) {
+            mSlider = slider;
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            mCurrSliderValue = progress;
+            long now = System.currentTimeMillis();
+            if (mLastSentSliderUpdate != 0
+                    && now - mLastSentSliderUpdate > SLIDER_UPDATE_INTERVAL) {
+                mSliderUpdaterRunning = false;
+                mSliderUpdateHandler.removeCallbacks(mSliderUpdater);
+                sendSliderValue();
+            } else if (!mSliderUpdaterRunning) {
+                mSliderUpdaterRunning = true;
+                mSliderUpdateHandler.postDelayed(mSliderUpdater, SLIDER_UPDATE_INTERVAL);
+            }
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            if (mSliderUpdaterRunning) {
+                mSliderUpdaterRunning = false;
+                mSliderUpdateHandler.removeCallbacks(mSliderUpdater);
+            }
+            mCurrSliderValue = seekBar.getProgress();
+            sendSliderValue();
+        }
+
+        private void sendSliderValue() {
+            if (mSlider == null) {
+                return;
+            }
+            mLastSentSliderUpdate = System.currentTimeMillis();
+            Intent intent = new Intent();
+            intent.putExtra(QC_ACTION_SLIDER_VALUE, mCurrSliderValue);
+            fireAction(mSlider, intent);
+        }
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCTileView.java b/car-qc-lib/src/com/android/car/qc/view/QCTileView.java
new file mode 100644
index 0000000..233c81a
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCTileView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.android.car.qc.QCItem.QC_ACTION_TOGGLE_STATE;
+import static com.android.car.qc.view.QCView.QCActionListener;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.lifecycle.Observer;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCTile;
+import com.android.car.qc.R;
+import com.android.car.ui.uxr.DrawableStateToggleButton;
+
+/**
+ * Quick Controls view for {@link QCTile} instances.
+ */
+public class QCTileView extends FrameLayout implements Observer<QCItem> {
+    private static final String TAG = "QCTileView";
+
+    private View mTileWrapper;
+    private DrawableStateToggleButton mToggleButton;
+    private TextView mSubtitle;
+    private QCActionListener mActionListener;
+
+    public QCTileView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public QCTileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public QCTileView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context);
+    }
+
+    public QCTileView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context);
+    }
+
+    /**
+     * Set the tile's {@link QCActionListener}.
+     */
+    public void setActionListener(QCActionListener listener) {
+        mActionListener = listener;
+    }
+
+    private void init(Context context) {
+        View.inflate(context, R.layout.qc_tile_view, /* root= */ this);
+        mTileWrapper = findViewById(R.id.qc_tile_wrapper);
+        mToggleButton = findViewById(R.id.qc_tile_toggle_button);
+        mSubtitle = findViewById(android.R.id.summary);
+        mToggleButton.setText(null);
+        mToggleButton.setTextOn(null);
+        mToggleButton.setTextOff(null);
+
+    }
+
+    @Override
+    public void onChanged(QCItem qcItem) {
+        if (qcItem == null) {
+            removeAllViews();
+            return;
+        }
+        if (!qcItem.getType().equals(QCItem.QC_TYPE_TILE)) {
+            throw new IllegalArgumentException("Expected QCTile type for QCTileView but got "
+                    + qcItem.getType());
+        }
+        QCTile qcTile = (QCTile) qcItem;
+        mSubtitle.setText(qcTile.getSubtitle());
+        mToggleButton.setOnCheckedChangeListener(null);
+        mToggleButton.setChecked(qcTile.isChecked());
+        mToggleButton.setEnabled(qcTile.isEnabled());
+        mTileWrapper.setEnabled(qcTile.isEnabled() && qcTile.isAvailable());
+        mTileWrapper.setOnClickListener(v -> mToggleButton.toggle());
+        Drawable icon = QCViewUtils.getInstance(mContext).getToggleIcon(
+                qcTile.getIcon(), qcTile.isAvailable());
+        mToggleButton.setButtonDrawable(icon);
+        mToggleButton.setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    Intent intent = new Intent();
+                    intent.putExtra(QC_ACTION_TOGGLE_STATE, isChecked);
+                    if (qcTile.getPrimaryAction() != null) {
+                        try {
+                            qcTile.getPrimaryAction().send(getContext(), 0, intent);
+                            if (mActionListener != null) {
+                                mActionListener.onQCAction(qcTile);
+                            }
+                        } catch (PendingIntent.CanceledException e) {
+                            Log.d(TAG, "Error sending intent", e);
+                        }
+                    } else if (qcTile.getActionHandler() != null) {
+                        qcTile.getActionHandler().onAction(qcTile, getContext(), intent);
+                        if (mActionListener != null) {
+                            mActionListener.onQCAction(qcTile);
+                        }
+                    }
+                });
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCView.java b/car-qc-lib/src/com/android/car/qc/view/QCView.java
new file mode 100644
index 0000000..73b909e
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCView.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Observer;
+
+import com.android.car.qc.QCItem;
+
+/**
+ * Base Quick Controls View - supports {@link QCItem.QC_TYPE_TILE} and {@link QCItem.QC_TYPE_LIST}
+ */
+public class QCView extends FrameLayout implements Observer<QCItem> {
+    @QCItem.QCItemType
+    private String mType;
+    private Observer<QCItem> mChildObserver;
+    private QCActionListener mActionListener;
+
+    public QCView(Context context) {
+        super(context);
+    }
+
+    public QCView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public QCView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public QCView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Set the view's {@link QCActionListener}. This listener will propagate to all sub-views.
+     */
+    public void setActionListener(QCActionListener listener) {
+        mActionListener = listener;
+        if (mChildObserver instanceof QCTileView) {
+            ((QCTileView) mChildObserver).setActionListener(mActionListener);
+        } else if (mChildObserver instanceof QCListView) {
+            ((QCListView) mChildObserver).setActionListener(mActionListener);
+        }
+    }
+
+    @Override
+    public void onChanged(QCItem qcItem) {
+        if (qcItem == null) {
+            removeAllViews();
+            mChildObserver = null;
+            mType = null;
+            return;
+        }
+        if (!isValidQCItemType(qcItem)) {
+            throw new IllegalArgumentException("Expected QCTile or QCList type but got "
+                    + qcItem.getType());
+        }
+        if (qcItem.getType().equals(mType)) {
+            mChildObserver.onChanged(qcItem);
+            return;
+        }
+        removeAllViews();
+        mType = qcItem.getType();
+        if (mType.equals(QCItem.QC_TYPE_TILE)) {
+            QCTileView view = new QCTileView(getContext());
+            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                    LayoutParams.WRAP_CONTENT,
+                    LayoutParams.WRAP_CONTENT,
+                    Gravity.CENTER_HORIZONTAL);
+            view.onChanged(qcItem);
+            view.setActionListener(mActionListener);
+            addView(view, params);
+            mChildObserver = view;
+        } else {
+            QCListView view = new QCListView(getContext());
+            view.onChanged(qcItem);
+            view.setActionListener(mActionListener);
+            addView(view);
+            mChildObserver = view;
+        }
+    }
+
+    private boolean isValidQCItemType(QCItem qcItem) {
+        String type = qcItem.getType();
+        return type.equals(QCItem.QC_TYPE_TILE) || type.equals(QCItem.QC_TYPE_LIST);
+    }
+
+    /**
+     * Listener to be called when an action occurs on a QCView.
+     */
+    public interface QCActionListener {
+        /**
+         * Called when an interaction has occurred with an element in this view.
+         * @param item the specific item within the {@link QCItem} that was interacted with.
+         */
+        void onQCAction(@NonNull QCItem item);
+    }
+}
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java b/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java
new file mode 100644
index 0000000..366c724
--- /dev/null
+++ b/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.qc.R;
+
+/**
+ * Utility class used by {@link QCTileView} and {@link QCRowView}
+ */
+public class QCViewUtils {
+    private static QCViewUtils sInstance;
+
+    private final Context mContext;
+    private final Drawable mDefaultToggleBackground;
+    private final Drawable mUnavailableToggleBackground;
+    private final ColorStateList mDefaultToggleIconTint;
+    @ColorInt
+    private final int mUnavailableToggleIconTint;
+    private final int mToggleForegroundIconInset;
+
+    private QCViewUtils(@NonNull Context context) {
+        mContext = context.getApplicationContext();
+        mDefaultToggleBackground = mContext.getDrawable(R.drawable.qc_toggle_background);
+        mUnavailableToggleBackground = mContext.getDrawable(
+                R.drawable.qc_toggle_unavailable_background);
+        mDefaultToggleIconTint = mContext.getColorStateList(R.color.qc_toggle_icon_fill_color);
+        mUnavailableToggleIconTint = mContext.getColor(R.color.qc_toggle_unavailable_color);
+        mToggleForegroundIconInset = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.qc_toggle_foreground_icon_inset);
+    }
+
+    /**
+     * Get an instance of {@link QCViewUtils}
+     */
+    public static QCViewUtils getInstance(@NonNull Context context) {
+        if (sInstance == null) {
+            sInstance = new QCViewUtils(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a return a Quick Control toggle icon - used for tiles and action toggles.
+     */
+    public Drawable getToggleIcon(@Nullable Icon icon, boolean available) {
+        Drawable background = available
+                ? mDefaultToggleBackground.getConstantState().newDrawable().mutate()
+                : mUnavailableToggleBackground.getConstantState().newDrawable().mutate();
+        if (icon == null) {
+            return background;
+        }
+
+        Drawable iconDrawable = icon.loadDrawable(mContext);
+        if (iconDrawable == null) {
+            return background;
+        }
+
+        if (!available) {
+            iconDrawable.setTint(mUnavailableToggleIconTint);
+        } else {
+            iconDrawable.setTintList(mDefaultToggleIconTint);
+        }
+
+        Drawable[] layers = {background, iconDrawable};
+        LayerDrawable drawable = new LayerDrawable(layers);
+        drawable.setLayerInsetRelative(/* index= */ 1, mToggleForegroundIconInset,
+                mToggleForegroundIconInset, mToggleForegroundIconInset,
+                mToggleForegroundIconInset);
+        return drawable;
+    }
+}
diff --git a/car-messenger-common/tests/unit/Android.bp b/car-qc-lib/tests/unit/Android.bp
similarity index 75%
rename from car-messenger-common/tests/unit/Android.bp
rename to car-qc-lib/tests/unit/Android.bp
index eb31d43..b1f107a 100644
--- a/car-messenger-common/tests/unit/Android.bp
+++ b/car-qc-lib/tests/unit/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2019 The Android Open Source Project
+// Copyright (C) 2021 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.
@@ -12,14 +12,16 @@
 // 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 {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 android_test {
-    name: "car-messenger-common-lib-unit-tests",
+    name: "CarQCLibUnitTests",
+
+    certificate: "platform",
+    privileged: true,
 
     srcs: ["src/**/*.java"],
 
@@ -30,14 +32,16 @@
     ],
 
     static_libs: [
-        "android.car",
+        "car-qc-lib",
         "androidx.test.core",
-        "androidx.test.ext.junit",
         "androidx.test.rules",
-        "car-messenger-common",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
         "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
         "truth-prebuilt",
+        "testng",
     ],
 
-    platform_apis: true,
+    jni_libs: ["libdexmakerjvmtiagent", "libstaticjvmtiagent"],
 }
diff --git a/car-qc-lib/tests/unit/AndroidManifest.xml b/car-qc-lib/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..e500c4d
--- /dev/null
+++ b/car-qc-lib/tests/unit/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.qc.tests.unit">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <provider
+            android:name="com.android.car.qc.testutils.AllowedTestQCProvider"
+            android:authorities="com.android.car.qc.testutils.AllowedTestQCProvider"
+            android:exported="true">
+        </provider>
+
+        <provider
+            android:name="com.android.car.qc.testutils.DeniedTestQCProvider"
+            android:authorities="com.android.car.qc.testutils.DeniedTestQCProvider"
+            android:exported="true">
+        </provider>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.car.qc.tests.unit"
+                     android:label="Quick Controls Library Unit Tests"/>
+</manifest>
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
new file mode 100644
index 0000000..588144f
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_TOGGLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
+
+    @Test
+    public void onCreate_invalidType_throwsException() {
+        assertThrows(IllegalArgumentException.class,
+                () -> createAction("INVALID_TYPE", /* action= */ null, /* icon= */ null));
+    }
+
+    @Test
+    public void onCreateSwitch_hasCorrectType() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, /* action= */ null,
+                /* icon= */null);
+        assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
+    }
+
+    @Test
+    public void onCreateToggle_hasCorrectType() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
+                /* icon= */ null);
+        assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
+    }
+
+    @Test
+    public void onBundle_nullAction_noCrash() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null, mDefaultIcon);
+        writeAndLoadFromBundle(action);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void onBundle_nullIcon_noCrash() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction, /* icon= */ null);
+        writeAndLoadFromBundle(action);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void onBundle_switch_accurateData() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, mDefaultAction, /* icon= */ null);
+        QCActionItem newAction = writeAndLoadFromBundle(action);
+        assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
+        assertThat(newAction.isChecked()).isTrue();
+        assertThat(newAction.isEnabled()).isTrue();
+        assertThat(newAction.getPrimaryAction()).isNotNull();
+        assertThat(newAction.getIcon()).isNull();
+    }
+
+    @Test
+    public void onBundle_toggle_accurateDate() {
+        QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction, mDefaultIcon);
+        QCActionItem newAction = writeAndLoadFromBundle(action);
+        assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
+        assertThat(newAction.isChecked()).isTrue();
+        assertThat(newAction.isEnabled()).isTrue();
+        assertThat(newAction.getPrimaryAction()).isNotNull();
+        assertThat(newAction.getIcon()).isNotNull();
+    }
+
+    private QCActionItem createAction(String type, PendingIntent action, Icon icon) {
+        return new QCActionItem.Builder(type)
+                .setChecked(true)
+                .setEnabled(true)
+                .setAction(action)
+                .setIcon(icon)
+                .build();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCItemTestCase.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCItemTestCase.java
new file mode 100644
index 0000000..fab8302
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCItemTestCase.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import android.R;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
+
+public abstract class QCItemTestCase<T extends QCItem> {
+    protected static final String BUNDLE_KEY = "BUNDLE_KEY";
+    protected static final String TEST_TITLE = "TEST TITLE";
+    protected static final String TEST_SUBTITLE = "TEST SUBTITLE";
+
+    protected final Context mContext = ApplicationProvider.getApplicationContext();
+
+    protected PendingIntent mDefaultAction = PendingIntent.getActivity(mContext,
+            /* requestCode= */ 0, new Intent(), PendingIntent.FLAG_IMMUTABLE);
+    protected Icon mDefaultIcon = Icon.createWithResource(mContext, R.drawable.btn_star);
+
+    protected T writeAndLoadFromBundle(T item) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(BUNDLE_KEY, item);
+        return bundle.getParcelable(BUNDLE_KEY);
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCListTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCListTest.java
new file mode 100644
index 0000000..766d82c
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCListTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import static com.android.car.qc.QCItem.QC_TYPE_LIST;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class QCListTest extends QCItemTestCase<QCList> {
+
+    @Test
+    public void onCreate_hasCorrectType() {
+        QCList list = createList(Collections.emptyList());
+        assertThat(list.getType()).isEqualTo(QC_TYPE_LIST);
+    }
+
+    @Test
+    public void createFromParcel_accurateData() {
+        QCRow row = new QCRow.Builder()
+                .setTitle(TEST_TITLE)
+                .setSubtitle(TEST_SUBTITLE)
+                .setIcon(mDefaultIcon)
+                .setPrimaryAction(mDefaultAction)
+                .build();
+
+        QCList list = createList(Collections.singletonList(row));
+        QCList newList = writeAndLoadFromBundle(list);
+        assertThat(newList.getType()).isEqualTo(QC_TYPE_LIST);
+        assertThat(newList.getRows().size()).isEqualTo(1);
+    }
+
+    private QCList createList(List<QCRow> rows) {
+        QCList.Builder builder = new QCList.Builder();
+        for (QCRow row : rows) {
+            builder.addRow(row);
+        }
+        return builder.build();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCRowTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCRowTest.java
new file mode 100644
index 0000000..816c1d6
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCRowTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
+import static com.android.car.qc.QCItem.QC_TYPE_ROW;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class QCRowTest extends QCItemTestCase<QCRow> {
+
+    @Test
+    public void onCreate_hasCorrectType() {
+        QCRow row = createRow(/* action= */ null, /* icon= */ null);
+        assertThat(row.getType()).isEqualTo(QC_TYPE_ROW);
+    }
+
+    @Test
+    public void onBundle_nullAction_noCrash() {
+        QCRow row = createRow(/* action= */ null, mDefaultIcon);
+        writeAndLoadFromBundle(row);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void onBundle_nullIcon_noCrash() {
+        QCRow row = createRow(mDefaultAction, /* icon= */ null);
+        writeAndLoadFromBundle(row);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void createFromParcel_accurateData() {
+        QCRow row = createRow(mDefaultAction, mDefaultIcon);
+        QCRow newRow = writeAndLoadFromBundle(row);
+        assertThat(newRow.getType()).isEqualTo(QC_TYPE_ROW);
+        assertThat(newRow.getTitle()).isEqualTo(TEST_TITLE);
+        assertThat(newRow.getSubtitle()).isEqualTo(TEST_SUBTITLE);
+        assertThat(newRow.getPrimaryAction()).isNotNull();
+        assertThat(newRow.getStartIcon()).isNotNull();
+    }
+
+    @Test
+    public void createFromParcel_accurateData_startItem() {
+        QCActionItem item = new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build();
+
+        QCRow row = createRow(/* action= */ null, /* icon= */ null, Collections.singletonList(item),
+                Collections.emptyList(), Collections.emptyList());
+        QCRow newRow = writeAndLoadFromBundle(row);
+        assertThat(newRow.getStartItems().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void createFromParcel_accurateData_endItem() {
+        QCActionItem item = new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build();
+
+        QCRow row = createRow(/* action= */ null, /* icon= */ null, Collections.emptyList(),
+                Collections.singletonList(item), Collections.emptyList());
+        QCRow newRow = writeAndLoadFromBundle(row);
+        assertThat(newRow.getEndItems().size()).isEqualTo(1);
+    }
+
+    @Test
+    public void createFromParcel_accurateData_slider() {
+        QCSlider slider = new QCSlider.Builder().build();
+
+        QCRow row = createRow(/* action= */ null, /* icon= */ null, Collections.emptyList(),
+                Collections.emptyList(), Collections.singletonList(slider));
+        QCRow newRow = writeAndLoadFromBundle(row);
+        assertThat(newRow.getSlider()).isNotNull();
+    }
+
+    private QCRow createRow(PendingIntent action, Icon icon) {
+        return createRow(action, icon, Collections.emptyList(), Collections.emptyList(),
+                Collections.emptyList());
+    }
+
+    private QCRow createRow(PendingIntent action, Icon icon, List<QCActionItem> startItems,
+            List<QCActionItem> endItems, List<QCSlider> sliders) {
+        QCRow.Builder builder = new QCRow.Builder()
+                .setTitle(TEST_TITLE)
+                .setSubtitle(TEST_SUBTITLE)
+                .setIcon(icon)
+                .setPrimaryAction(action);
+        for (QCActionItem item : startItems) {
+            builder.addStartItem(item);
+        }
+        for (QCActionItem item : endItems) {
+            builder.addEndItem(item);
+        }
+        for (QCSlider slider : sliders) {
+            builder.addSlider(slider);
+        }
+        return builder.build();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCSliderTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCSliderTest.java
new file mode 100644
index 0000000..97bf191
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCSliderTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import static com.android.car.qc.QCItem.QC_TYPE_SLIDER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCSliderTest extends QCItemTestCase<QCSlider> {
+    private static final int MIN = 50;
+    private static final int MAX = 150;
+    private static final int VALUE = 75;
+
+    @Test
+    public void onCreate_hasCorrectType() {
+        QCSlider slider = createSlider(/* action= */ null);
+        assertThat(slider.getType()).isEqualTo(QC_TYPE_SLIDER);
+    }
+
+    @Test
+    public void onBundle_nullAction_noCrash() {
+        QCSlider slider = createSlider(/* action= */ null);
+        writeAndLoadFromBundle(slider);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void createFromParcel_accurateData() {
+        QCSlider slider = createSlider(mDefaultAction);
+        QCSlider newSlider = writeAndLoadFromBundle(slider);
+        assertThat(newSlider.getType()).isEqualTo(QC_TYPE_SLIDER);
+        assertThat(newSlider.getPrimaryAction()).isNotNull();
+        assertThat(newSlider.getMin()).isEqualTo(MIN);
+        assertThat(newSlider.getMax()).isEqualTo(MAX);
+        assertThat(newSlider.getValue()).isEqualTo(VALUE);
+    }
+
+    private QCSlider createSlider(PendingIntent action) {
+        return new QCSlider.Builder()
+                .setMin(MIN)
+                .setMax(MAX)
+                .setValue(VALUE)
+                .setInputAction(action)
+                .build();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCTileTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCTileTest.java
new file mode 100644
index 0000000..e9530d5
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCTileTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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.qc;
+
+import static com.android.car.qc.QCItem.QC_TYPE_TILE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCTileTest extends QCItemTestCase<QCTile> {
+
+    @Test
+    public void onCreate_hasCorrectType() {
+        QCTile tile = createTile(/* action= */ null, /* icon= */ null);
+        assertThat(tile.getType()).isEqualTo(QC_TYPE_TILE);
+    }
+
+    @Test
+    public void onBundle_nullAction_noCrash() {
+        QCTile tile = createTile(/* action= */ null, mDefaultIcon);
+        writeAndLoadFromBundle(tile);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void onBundle_nullIcon_noCrash() {
+        QCTile tile = createTile(mDefaultAction, /* icon= */ null);
+        writeAndLoadFromBundle(tile);
+        // Test passes if this doesn't crash
+    }
+
+    @Test
+    public void createFromParcel_accurateData() {
+        QCTile tile = createTile(mDefaultAction, mDefaultIcon);
+        QCTile newTile = writeAndLoadFromBundle(tile);
+        assertThat(newTile.getType()).isEqualTo(QC_TYPE_TILE);
+        assertThat(newTile.getSubtitle()).isEqualTo(TEST_SUBTITLE);
+        assertThat(newTile.isChecked()).isTrue();
+        assertThat(newTile.isEnabled()).isTrue();
+        assertThat(newTile.getPrimaryAction()).isNotNull();
+        assertThat(newTile.getIcon()).isNotNull();
+    }
+
+    private QCTile createTile(PendingIntent action, Icon icon) {
+        return new QCTile.Builder()
+                .setSubtitle(TEST_SUBTITLE)
+                .setChecked(true)
+                .setEnabled(true)
+                .setAction(action)
+                .setIcon(icon)
+                .build();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/controller/BaseQCControllerTestCase.java b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/BaseQCControllerTestCase.java
new file mode 100644
index 0000000..095b192
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/BaseQCControllerTestCase.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.lifecycle.Observer;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCTile;
+
+import org.junit.Test;
+
+public abstract class BaseQCControllerTestCase<T extends BaseQCController> {
+
+    protected final Context mContext = spy(ApplicationProvider.getApplicationContext());
+
+    protected abstract T getController();
+
+    @Test
+    public void listen_updateListeningCalled() {
+        T spiedController = spy(getController());
+        spiedController.listen(true);
+        verify(spiedController).updateListening();
+    }
+
+    @Test
+    public void addObserver_updateListeningCalled() {
+        Observer<QCItem> observer = mock(Observer.class);
+        T spiedController = spy(getController());
+        spiedController.addObserver(observer);
+        verify(spiedController).updateListening();
+    }
+
+    @Test
+    public void removeObserver_updateListeningCalled() {
+        Observer<QCItem> observer = mock(Observer.class);
+        T spiedController = spy(getController());
+        spiedController.removeObserver(observer);
+        verify(spiedController).updateListening();
+    }
+
+    @Test
+    public void onQCItemUpdated_observersNotified() {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().onQCItemUpdated(new QCTile.Builder().build());
+        verify(observer).onChanged(any(QCItem.class));
+    }
+
+    @Test
+    public void onDestroy_cleanUpController() {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        getController().destroy();
+        assertThat(getController().mObservers.size()).isEqualTo(0);
+        assertThat(getController().mShouldListen).isFalse();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/controller/LocalQCControllerTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/LocalQCControllerTest.java
new file mode 100644
index 0000000..17d7392
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/LocalQCControllerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import androidx.lifecycle.Observer;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.provider.BaseLocalQCProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@RunWith(AndroidJUnit4.class)
+public class LocalQCControllerTest extends BaseQCControllerTestCase<LocalQCController> {
+
+    private LocalQCController mController;
+    private BaseLocalQCProvider mProvider;
+
+    @Override
+    protected LocalQCController getController() {
+        if (mController == null) {
+            mProvider = mock(BaseLocalQCProvider.class);
+            mController = new LocalQCController(mContext, mProvider);
+        }
+        return mController;
+    }
+
+    @Test
+    public void onCreate_setsProviderNotifier() {
+        getController(); // instantiate
+        verify(mProvider).setNotifier(any());
+    }
+
+    @Test
+    public void updateListening_updatesProviderListening() {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        verify(mProvider).shouldListen(true);
+        getController().listen(false);
+        verify(mProvider).shouldListen(false);
+    }
+
+    @Test
+    public void updateListening_listen_updatesQCItem() {
+        Observer<QCItem> observer = mock(Observer.class);
+        LocalQCController spiedController = spy(getController());
+        spiedController.addObserver(observer);
+        Mockito.reset(mProvider);
+        spiedController.listen(true);
+        verify(mProvider).getQCItem();
+        verify(spiedController).onQCItemUpdated(any());
+    }
+
+    @Test
+    public void onDestroy_callsProviderDestroy() {
+        getController().destroy();
+        verify(mProvider).onDestroy();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/controller/RemoteQCControllerTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/RemoteQCControllerTest.java
new file mode 100644
index 0000000..a1db602
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/controller/RemoteQCControllerTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 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.qc.controller;
+
+import static com.android.car.qc.provider.BaseQCProvider.EXTRA_URI;
+import static com.android.car.qc.testutils.TestQCProvider.IS_DESTROYED_KEY;
+import static com.android.car.qc.testutils.TestQCProvider.IS_SUBSCRIBED_KEY;
+import static com.android.car.qc.testutils.TestQCProvider.KEY_DEFAULT;
+import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_DESTROYED;
+import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_SUBSCRIBED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.lifecycle.Observer;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.car.qc.QCItem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RemoteQCControllerTest extends BaseQCControllerTestCase<RemoteQCController> {
+
+    private final Uri mDefaultUri = Uri.parse(
+            "content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_DEFAULT);
+
+    private RemoteQCController mController;
+
+    @Override
+    protected RemoteQCController getController() {
+        if (mController == null) {
+            mController = new RemoteQCController(mContext, mDefaultUri, mContext.getMainExecutor());
+        }
+        return mController;
+    }
+
+    @Test
+    public void updateListening_listen_updatesQCItem() {
+        Observer<QCItem> observer = mock(Observer.class);
+        RemoteQCController spiedController = spy(getController());
+        spiedController.addObserver(observer);
+        spiedController.listen(true);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        verify(spiedController).onQCItemUpdated(notNull());
+    }
+
+    @Test
+    public void updateListening_listen_providerSubscribed() throws RemoteException {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        Bundle res = getController().getClient().call(METHOD_IS_SUBSCRIBED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, false);
+        assertThat(isSubscribed).isTrue();
+    }
+
+    @Test
+    public void updateListening_doNotListen_providerUnsubscribed() throws RemoteException {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        getController().listen(false);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        Bundle res = getController().getClient().call(METHOD_IS_SUBSCRIBED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, true);
+        assertThat(isSubscribed).isFalse();
+    }
+
+    @Test
+    public void updateListening_listen_registerContentObserver() {
+        ContentResolver resolver = mock(ContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        when(resolver.acquireContentProviderClient(mDefaultUri)).thenReturn(
+                mock(ContentProviderClient.class));
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        verify(resolver).registerContentObserver(eq(mDefaultUri), eq(true),
+                any(ContentObserver.class));
+    }
+
+    @Test
+    public void updateListening_doNotListen_unregisterContentObserver() {
+        ContentResolver resolver = mock(ContentResolver.class);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        when(resolver.acquireContentProviderClient(mDefaultUri)).thenReturn(
+                mock(ContentProviderClient.class));
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        getController().listen(false);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        verify(resolver).unregisterContentObserver(any(ContentObserver.class));
+    }
+
+    @Test
+    public void onDestroy_callsProviderOnDestroy() throws RemoteException {
+        Observer<QCItem> observer = mock(Observer.class);
+        getController().addObserver(observer);
+        getController().listen(true);
+        getController().listen(false);
+        getController().destroy();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        Bundle res = getController().getClient().call(METHOD_IS_DESTROYED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isDestroyed = res.getBoolean(IS_DESTROYED_KEY, false);
+        assertThat(isDestroyed).isTrue();
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseLocalQCProviderTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseLocalQCProviderTest.java
new file mode 100644
index 0000000..6defad7
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseLocalQCProviderTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.qc.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCTile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BaseLocalQCProviderTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private TestBaseLocalQCProvider mProvider;
+
+    @Before
+    public void setUp() {
+        mProvider = new TestBaseLocalQCProvider(mContext);
+    }
+
+    @Test
+    public void getQCItem_returnsItem() {
+        QCItem item = mProvider.getQCItem();
+        assertThat(item).isNotNull();
+        assertThat(item instanceof QCTile).isTrue();
+    }
+
+    @Test
+    public void listen_callsOnSubscribed() {
+        mProvider.shouldListen(true);
+        assertThat(mProvider.isSubscribed()).isTrue();
+    }
+
+    @Test
+    public void stopListening_callsOnUnsubscribed() {
+        mProvider.shouldListen(true);
+        mProvider.shouldListen(false);
+        assertThat(mProvider.isSubscribed()).isFalse();
+    }
+
+    @Test
+    public void notifyChange_updateNotified() {
+        BaseLocalQCProvider.Notifier notifier = mock(BaseLocalQCProvider.Notifier.class);
+        mProvider.setNotifier(notifier);
+        mProvider.notifyChange();
+        verify(notifier).notifyUpdate();
+    }
+
+    private static class TestBaseLocalQCProvider extends BaseLocalQCProvider {
+
+        private boolean mIsSubscribed;
+
+        TestBaseLocalQCProvider(Context context) {
+            super(context);
+        }
+
+        @Override
+        public QCItem getQCItem() {
+            return new QCTile.Builder().build();
+        }
+
+        @Override
+        protected void onSubscribed() {
+            mIsSubscribed = true;
+        }
+
+        @Override
+        protected void onUnsubscribed() {
+            mIsSubscribed = false;
+        }
+
+        boolean isSubscribed() {
+            return mIsSubscribed;
+        }
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseQCProviderTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseQCProviderTest.java
new file mode 100644
index 0000000..30607fa
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/provider/BaseQCProviderTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 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.qc.provider;
+
+import static com.android.car.qc.provider.BaseQCProvider.EXTRA_ITEM;
+import static com.android.car.qc.provider.BaseQCProvider.EXTRA_URI;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_BIND;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_DESTROY;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_SUBSCRIBE;
+import static com.android.car.qc.provider.BaseQCProvider.METHOD_UNSUBSCRIBE;
+import static com.android.car.qc.testutils.TestQCProvider.IS_DESTROYED_KEY;
+import static com.android.car.qc.testutils.TestQCProvider.IS_SUBSCRIBED_KEY;
+import static com.android.car.qc.testutils.TestQCProvider.KEY_DEFAULT;
+import static com.android.car.qc.testutils.TestQCProvider.KEY_SLOW;
+import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_DESTROYED;
+import static com.android.car.qc.testutils.TestQCProvider.METHOD_IS_SUBSCRIBED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCItem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BaseQCProviderTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private final Uri mDefaultUri = Uri.parse(
+            "content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_DEFAULT);
+    private final Uri mSlowUri =
+            Uri.parse("content://com.android.car.qc.testutils.AllowedTestQCProvider/" + KEY_SLOW);
+    private final Uri mDeniedUri =
+            Uri.parse("content://com.android.car.qc.testutils.DeniedTestQCProvider");
+
+    @Test
+    public void callOnBind_allowed_returnsItem() throws RemoteException {
+        ContentProviderClient provider = getClient(mDefaultUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        Bundle res = provider.call(METHOD_BIND, null, extras);
+        assertThat(res).isNotNull();
+        res.setClassLoader(QCItem.class.getClassLoader());
+        Parcelable parcelable = res.getParcelable(EXTRA_ITEM);
+        assertThat(parcelable).isNotNull();
+        assertThat(parcelable instanceof QCItem).isTrue();
+    }
+
+    @Test
+    public void callOnBind_noUri_throwsIllegalArgumentException() throws RemoteException {
+        ContentProviderClient provider = getClient(mDefaultUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        assertThrows(IllegalArgumentException.class,
+                () -> provider.call(METHOD_BIND, null, extras));
+    }
+
+    @Test
+    public void callOnBind_slowOperation_throwsRuntimeException() {
+        ContentProviderClient provider = getClient(mSlowUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mSlowUri);
+        assertThrows(RuntimeException.class,
+                () -> provider.call(METHOD_BIND, null, extras));
+    }
+
+    @Test
+    public void callOnBind_notAllowed_throwsSecurityException() {
+        ContentProviderClient provider = getClient(mDeniedUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDeniedUri);
+        assertThrows(SecurityException.class,
+                () -> provider.call(METHOD_BIND, null, extras));
+    }
+
+    @Test
+    public void callOnSubscribed_isSubscribed() throws RemoteException {
+        ContentProviderClient provider = getClient(mDefaultUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        provider.call(METHOD_SUBSCRIBE, null, extras);
+
+        Bundle res = provider.call(METHOD_IS_SUBSCRIBED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, false);
+        assertThat(isSubscribed).isTrue();
+    }
+
+    @Test
+    public void callOnUnsubscribed_isUnsubscribed() throws RemoteException {
+        ContentProviderClient provider = getClient(mDefaultUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        provider.call(METHOD_SUBSCRIBE, null, extras);
+        provider.call(METHOD_UNSUBSCRIBE, null, extras);
+
+        Bundle res = provider.call(METHOD_IS_SUBSCRIBED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isSubscribed = res.getBoolean(IS_SUBSCRIBED_KEY, true);
+        assertThat(isSubscribed).isFalse();
+    }
+
+    @Test
+    public void callDestroy_isDestroyed() throws RemoteException {
+        ContentProviderClient provider = getClient(mDefaultUri);
+        assertThat(provider).isNotNull();
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_URI, mDefaultUri);
+        provider.call(METHOD_SUBSCRIBE, null, extras);
+        provider.call(METHOD_UNSUBSCRIBE, null, extras);
+        provider.call(METHOD_DESTROY, null, extras);
+
+        Bundle res = provider.call(METHOD_IS_DESTROYED, null, extras);
+        assertThat(res).isNotNull();
+        boolean isDestroyed = res.getBoolean(IS_DESTROYED_KEY, false);
+        assertThat(isDestroyed).isTrue();
+    }
+
+    private ContentProviderClient getClient(Uri uri) {
+        return mContext.getContentResolver().acquireContentProviderClient(uri);
+    }
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/AllowedTestQCProvider.java
similarity index 63%
copy from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java
copy to car-qc-lib/tests/unit/src/com/android/car/qc/testutils/AllowedTestQCProvider.java
index a6e10f7..d3cbf87 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/SpanSizeLookupOEMV1.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/AllowedTestQCProvider.java
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
 
-/**
- * {@link androidx.recyclerview.widget.GridLayoutManager#setSpanSizeLookup}
- */
-public interface SpanSizeLookupOEMV1 {
+package com.android.car.qc.testutils;
 
-    /** {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup#getSpanSize} */
-    int getSpanSize(int position);
+import java.util.HashSet;
+import java.util.Set;
+
+public class AllowedTestQCProvider extends TestQCProvider {
+    @Override
+    protected Set<String> getAllowlistedPackages() {
+        Set<String> allowlist = new HashSet<>();
+        allowlist.add("com.android.car.qc.tests.unit");
+        return allowlist;
+    }
 }
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/MainActivity.java b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/DeniedTestQCProvider.java
similarity index 70%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/MainActivity.java
rename to car-qc-lib/tests/unit/src/com/android/car/qc/testutils/DeniedTestQCProvider.java
index 0549128..a9c56ce 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/MainActivity.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/DeniedTestQCProvider.java
@@ -13,13 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary;
 
-import android.app.Activity;
+package com.android.car.qc.testutils;
 
-/**
- * A blank activity. It's only purpose is for responding to the
- * {@code com.android.car.ui.intent.action.SHARED_LIBRARY} intent.
- */
-public class MainActivity extends Activity {
+import java.util.HashSet;
+import java.util.Set;
+
+public class DeniedTestQCProvider extends TestQCProvider {
+    @Override
+    protected Set<String> getAllowlistedPackages() {
+        return new HashSet<>();
+    }
 }
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/TestQCProvider.java b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/TestQCProvider.java
new file mode 100644
index 0000000..8248832
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/testutils/TestQCProvider.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.qc.testutils;
+
+import android.R;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+import com.android.car.qc.QCItem;
+import com.android.car.qc.QCTile;
+import com.android.car.qc.provider.BaseQCProvider;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class TestQCProvider extends BaseQCProvider {
+
+    public static final String METHOD_IS_SUBSCRIBED = "METHOD_IS_SUBSCRIBED";
+    public static final String IS_SUBSCRIBED_KEY = "IS_SUBSCRIBED";
+    public static final String METHOD_IS_DESTROYED = "METHOD_IS_DESTROYED";
+    public static final String IS_DESTROYED_KEY = "IS_DESTROYED";
+
+    public static final String KEY_DEFAULT = "DEFAULT";
+    public static final String KEY_SLOW = "SLOW";
+
+    private final Set<Uri> mSubscribedUris = new HashSet<>();
+    private final Set<Uri> mDestroyedUris = new HashSet<>();
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (METHOD_IS_SUBSCRIBED.equals(method)) {
+            Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_URI));
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(IS_SUBSCRIBED_KEY, mSubscribedUris.contains(uri));
+            return bundle;
+        }
+        if (METHOD_IS_DESTROYED.equals(method)) {
+            Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_URI));
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(IS_DESTROYED_KEY, mDestroyedUris.contains(uri));
+            return bundle;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    @Override
+    protected QCItem onBind(@NonNull Uri uri) {
+        List<String> pathSegments = uri.getPathSegments();
+        String key = pathSegments.get(0);
+
+        if (KEY_DEFAULT.equals(key)) {
+            return new QCTile.Builder()
+                    .setIcon(Icon.createWithResource(getContext(), R.drawable.btn_star))
+                    .build();
+        } else if (KEY_SLOW.equals(key)) {
+            // perform a slow operation that should trigger the strict thread policy
+            Drawable d = getContext().getDrawable(R.drawable.btn_star);
+            Bitmap bitmap = drawableToBitmap(d);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+            byte[] b = baos.toByteArray();
+            Icon icon = Icon.createWithData(b, 0, b.length);
+            return new QCTile.Builder()
+                    .setIcon(icon)
+                    .build();
+        }
+        return null;
+    }
+
+    @Override
+    protected void onSubscribed(@NonNull Uri uri) {
+        mSubscribedUris.add(uri);
+    }
+
+    @Override
+    protected void onUnsubscribed(@NonNull Uri uri) {
+        mSubscribedUris.remove(uri);
+    }
+
+    @Override
+    protected void onDestroy(@NonNull Uri uri) {
+        mDestroyedUris.add(uri);
+    }
+
+    private static Bitmap drawableToBitmap(Drawable drawable) {
+
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        return bitmap;
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCListViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCListViewTest.java
new file mode 100644
index 0000000..a1065e8
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCListViewTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCList;
+import com.android.car.qc.QCRow;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCListViewTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private QCListView mView;
+
+    @Before
+    public void setUp() {
+        mView = new QCListView(mContext);
+    }
+
+    @Test
+    public void onChanged_null_noViews() {
+        mView.onChanged(null);
+        assertThat(mView.getChildCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onChanged_invalidType_throwsIllegalArgumentException() {
+        QCRow row = new QCRow.Builder().build();
+        assertThrows(IllegalArgumentException.class,
+                () -> mView.onChanged(row));
+    }
+
+    @Test
+    public void onChanged_createsRows() {
+        QCList list = new QCList.Builder()
+                .addRow(new QCRow.Builder().build())
+                .addRow(new QCRow.Builder().build())
+                .build();
+        mView.onChanged(list);
+        assertThat(mView.getChildCount()).isEqualTo(2);
+        assertThat(mView.getChildAt(0) instanceof QCRowView).isTrue();
+        assertThat(mView.getChildAt(1) instanceof QCRowView).isTrue();
+    }
+
+    @Test
+    public void onChanged_decreasedRowCount_removesExtraRows() {
+        QCList list = new QCList.Builder()
+                .addRow(new QCRow.Builder().build())
+                .addRow(new QCRow.Builder().build())
+                .build();
+        mView.onChanged(list);
+        assertThat(mView.getChildCount()).isEqualTo(2);
+        list = new QCList.Builder()
+                .addRow(new QCRow.Builder().build())
+                .build();
+        mView.onChanged(list);
+        assertThat(mView.getChildCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void setActionListener_setsOnChildView() {
+        QCList list = new QCList.Builder()
+                .addRow(new QCRow.Builder().build())
+                .addRow(new QCRow.Builder().build())
+                .build();
+        mView.onChanged(list);
+        assertThat(mView.getChildCount()).isEqualTo(2);
+        QCRowView row1 = (QCRowView) mView.getChildAt(0);
+        QCRowView row2 = (QCRowView) mView.getChildAt(1);
+        ExtendedMockito.spyOn(row1);
+        ExtendedMockito.spyOn(row2);
+        QCView.QCActionListener listener = mock(QCView.QCActionListener.class);
+        mView.setActionListener(listener);
+        ExtendedMockito.verify(row1).setActionListener(listener);
+        ExtendedMockito.verify(row2).setActionListener(listener);
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
new file mode 100644
index 0000000..6e1639c
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_SWITCH;
+import static com.android.car.qc.QCItem.QC_TYPE_ACTION_TOGGLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCActionItem;
+import com.android.car.qc.QCRow;
+import com.android.car.qc.QCSlider;
+import com.android.car.qc.R;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCRowViewTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private QCRowView mView;
+
+    @Before
+    public void setUp() {
+        mView = new QCRowView(mContext);
+    }
+
+    @Test
+    public void setRow_null_notVisible() {
+        mView.setRow(null);
+        assertThat(mView.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setRow_notNull_visible() {
+        QCRow row = new QCRow.Builder().build();
+        mView.setRow(row);
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void setRow_setsTitle() {
+        String title = "TEST_TITLE";
+        QCRow row = new QCRow.Builder().setTitle(title).build();
+        mView.setRow(row);
+        TextView titleView = mView.findViewById(R.id.qc_title);
+        assertThat(titleView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(titleView.getText().toString()).isEqualTo(title);
+    }
+
+    @Test
+    public void setRow_setsSubtitle() {
+        String subtitle = "TEST_TITLE";
+        QCRow row = new QCRow.Builder().setSubtitle(subtitle).build();
+        mView.setRow(row);
+        TextView subtitleView = mView.findViewById(R.id.qc_summary);
+        assertThat(subtitleView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(subtitleView.getText().toString()).isEqualTo(subtitle);
+    }
+
+    @Test
+    public void setRow_setsIcon() {
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.btn_star);
+        QCRow row = new QCRow.Builder().setIcon(icon).build();
+        mView.setRow(row);
+        ImageView iconView = mView.findViewById(R.id.qc_icon);
+        assertThat(iconView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(iconView.getDrawable()).isNotNull();
+    }
+
+    @Test
+    public void setRow_createsStartItems() {
+        QCRow row = new QCRow.Builder()
+                .addStartItem(new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build())
+                .addStartItem(new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).build())
+                .build();
+        mView.setRow(row);
+        LinearLayout startContainer = mView.findViewById(R.id.qc_row_start_items);
+        assertThat(startContainer.getChildCount()).isEqualTo(2);
+        assertThat((View) startContainer.getChildAt(0).findViewById(
+                android.R.id.switch_widget)).isNotNull();
+        assertThat((View) startContainer.getChildAt(1).findViewById(
+                R.id.qc_toggle_button)).isNotNull();
+    }
+
+    @Test
+    public void setRow_createsEndItems() {
+        QCRow row = new QCRow.Builder()
+                .addEndItem(new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).build())
+                .addEndItem(new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).build())
+                .build();
+        mView.setRow(row);
+        LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
+        assertThat(endContainer.getChildCount()).isEqualTo(2);
+        assertThat((View) endContainer.getChildAt(0).findViewById(
+                android.R.id.switch_widget)).isNotNull();
+        assertThat((View) endContainer.getChildAt(1).findViewById(
+                R.id.qc_toggle_button)).isNotNull();
+    }
+
+    @Test
+    public void setRow_noSlider_sliderViewNotVisible() {
+        QCRow row = new QCRow.Builder().build();
+        mView.setRow(row);
+        LinearLayout sliderContainer = mView.findViewById(R.id.qc_seekbar_wrapper);
+        assertThat(sliderContainer.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    @UiThreadTest
+    public void setRow_hasSlider_sliderViewVisible() {
+        QCRow row = new QCRow.Builder()
+                .addSlider(new QCSlider.Builder().build())
+                .build();
+        mView.setRow(row);
+        LinearLayout sliderContainer = mView.findViewById(R.id.qc_seekbar_wrapper);
+        assertThat(sliderContainer.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onRowClick_firesAction() throws PendingIntent.CanceledException {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder().setPrimaryAction(action).build();
+        mView.setRow(row);
+        mView.findViewById(R.id.qc_row_content).performClick();
+        verify(action).send(any(Context.class), anyInt(), eq(null));
+    }
+
+    @Test
+    public void onSwitchClick_firesAction() throws PendingIntent.CanceledException {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder()
+                .addEndItem(
+                        new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).setAction(action).build())
+                .build();
+        mView.setRow(row);
+        LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
+        assertThat(endContainer.getChildCount()).isEqualTo(1);
+        endContainer.getChildAt(0).performClick();
+        verify(action).send(any(Context.class), anyInt(), any(Intent.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void onToggleClick_firesAction() throws PendingIntent.CanceledException {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder()
+                .addEndItem(
+                        new QCActionItem.Builder(QC_TYPE_ACTION_TOGGLE).setAction(action).build())
+                .build();
+        mView.setRow(row);
+        LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
+        assertThat(endContainer.getChildCount()).isEqualTo(1);
+        endContainer.getChildAt(0).performClick();
+        verify(action).send(any(Context.class), anyInt(), any(Intent.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void onSliderChange_firesAction() throws PendingIntent.CanceledException {
+        PendingIntent action = mock(PendingIntent.class);
+        QCRow row = new QCRow.Builder()
+                .addSlider(new QCSlider.Builder().setInputAction(action).build())
+                .build();
+        mView.setRow(row);
+        SeekBar seekBar = mView.findViewById(R.id.seekbar);
+        seekBar.setProgress(50);
+        MotionEvent motionEvent = ExtendedMockito.mock(MotionEvent.class);
+        ExtendedMockito.when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+        seekBar.onTouchEvent(motionEvent);
+        verify(action).send(any(Context.class), anyInt(), any(Intent.class));
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCTileViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCTileViewTest.java
new file mode 100644
index 0000000..9104520
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCTileViewTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
+import android.widget.TextView;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCRow;
+import com.android.car.qc.QCTile;
+import com.android.car.qc.R;
+import com.android.car.ui.uxr.DrawableStateToggleButton;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCTileViewTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private QCTileView mView;
+
+    @Before
+    public void setUp() {
+        mView = new QCTileView(mContext);
+    }
+
+    @Test
+    public void onChanged_null_noViews() {
+        mView.onChanged(null);
+        assertThat(mView.getChildCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onChanged_invalidType_throwsIllegalArgumentException() {
+        QCRow row = new QCRow.Builder().build();
+        assertThrows(IllegalArgumentException.class,
+                () -> mView.onChanged(row));
+    }
+
+    @Test
+    public void onChanged_setsSubtitleView() {
+        String subtitle = "TEST_SUBTITLE";
+        QCTile tile = new QCTile.Builder().setSubtitle(subtitle).build();
+        mView.onChanged(tile);
+        TextView subtitleView = mView.findViewById(android.R.id.summary);
+        assertThat(subtitleView.getText().toString()).isEqualTo(subtitle);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onChanged_setsButtonState() {
+        QCTile tile = new QCTile.Builder().setChecked(true).setEnabled(true).build();
+        mView.onChanged(tile);
+        DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
+        assertThat(button.isEnabled()).isTrue();
+        assertThat(button.isChecked()).isTrue();
+    }
+
+    @Test
+    public void onChanged_setsIcon() {
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.btn_star);
+        QCTile tile = new QCTile.Builder().setIcon(icon).build();
+        mView.onChanged(tile);
+        DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
+        Drawable buttonDrawable = button.getButtonDrawable();
+        assertThat(buttonDrawable).isNotNull();
+        assertThat(buttonDrawable instanceof LayerDrawable).isTrue();
+        assertThat(((LayerDrawable) buttonDrawable).getNumberOfLayers()).isEqualTo(2);
+    }
+
+    @Test
+    @UiThreadTest
+    public void onClick_firesAction() throws PendingIntent.CanceledException {
+        PendingIntent action = mock(PendingIntent.class);
+        QCTile tile = new QCTile.Builder().setChecked(false).setAction(action).build();
+        mView.onChanged(tile);
+        mView.findViewById(R.id.qc_tile_wrapper).performClick();
+        DrawableStateToggleButton button = mView.findViewById(R.id.qc_tile_toggle_button);
+        assertThat(button.isChecked()).isTrue();
+        verify(action).send(any(Context.class), anyInt(), any(Intent.class));
+    }
+}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCViewTest.java
new file mode 100644
index 0000000..b2090cb
--- /dev/null
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCViewTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.qc.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.qc.QCList;
+import com.android.car.qc.QCRow;
+import com.android.car.qc.QCTile;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class QCViewTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private QCView mView;
+
+    @Before
+    public void setUp() {
+        mView = new QCView(mContext);
+    }
+
+    @Test
+    public void onChanged_null_noViews() {
+        mView.onChanged(null);
+        assertThat(mView.getChildCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void onChanged_invalidType_throwsIllegalArgumentException() {
+        QCRow row = new QCRow.Builder().build();
+        assertThrows(IllegalArgumentException.class,
+                () -> mView.onChanged(row));
+    }
+
+    @Test
+    public void onChanged_list_createsListView() {
+        QCList list = new QCList.Builder().build();
+        mView.onChanged(list);
+        assertThat(mView.getChildCount()).isEqualTo(1);
+        assertThat(mView.getChildAt(0) instanceof QCListView).isTrue();
+    }
+
+    @Test
+    public void onChanged_tile_createsTileView() {
+        QCTile tile = new QCTile.Builder().build();
+        mView.onChanged(tile);
+        assertThat(mView.getChildCount()).isEqualTo(1);
+        assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
+    }
+
+    @Test
+    public void onChanged_alreadyHasView_callsOnChanged() {
+        QCTile tile = new QCTile.Builder().build();
+        mView.onChanged(tile);
+        assertThat(mView.getChildCount()).isEqualTo(1);
+        assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
+        QCTileView tileView = (QCTileView) mView.getChildAt(0);
+        ExtendedMockito.spyOn(tileView);
+        mView.onChanged(tile);
+        verify(tileView).onChanged(tile);
+    }
+
+    @Test
+    public void setActionListener_setsOnChildView() {
+        QCTile tile = new QCTile.Builder().build();
+        mView.onChanged(tile);
+        assertThat(mView.getChildCount()).isEqualTo(1);
+        assertThat(mView.getChildAt(0) instanceof QCTileView).isTrue();
+        QCTileView tileView = (QCTileView) mView.getChildAt(0);
+        ExtendedMockito.spyOn(tileView);
+        QCView.QCActionListener listener = mock(QCView.QCActionListener.class);
+        mView.setActionListener(listener);
+        ExtendedMockito.verify(tileView).setActionListener(listener);
+    }
+}
diff --git a/car-telephony-common/Android.bp b/car-telephony-common/Android.bp
index 843abc5..28b6f07 100644
--- a/car-telephony-common/Android.bp
+++ b/car-telephony-common/Android.bp
@@ -31,35 +31,16 @@
 
     sdk_version: "system_current",
     min_sdk_version: "28",
+    target_sdk_version: "31",
 
     static_libs: [
         "androidx.legacy_legacy-support-v4",
         "car-apps-common",
         "glide-prebuilt",
+        "guava",
         "libphonenumber",
     ],
 
     libs: ["android.car-system-stubs"],
 }
 
-// Used by instrumented test
-android_app {
-    name: "CarTelephonyLibTestsApp",
-
-    resource_dirs: ["res"],
-
-    srcs: ["src/**/*.java"],
-
-    static_libs: [
-        "androidx.legacy_legacy-support-v4",
-        "car-apps-common",
-        "glide-prebuilt",
-        "libphonenumber",
-    ],
-
-    optimize: {
-        enabled: false,
-    },
-
-    sdk_version: "system_current",
-}
diff --git a/car-telephony-common/build.gradle b/car-telephony-common/build.gradle
new file mode 100644
index 0000000..072bff2
--- /dev/null
+++ b/car-telephony-common/build.gradle
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+
+        androidTest.setRoot('tests/unittests')
+        androidTest.java.srcDirs = ['tests/unittests/src']
+    }
+
+    testOptions {
+        animationsDisabled = true
+    }
+}
+
+dependencies {
+    runtimeOnly 'androidx.legacy:legacy-support-v4:1.0.0'
+
+    implementation 'com.github.bumptech.glide:glide:4.12.0'
+    implementation 'com.google.guava:guava:30.1.1-android'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
+
+    implementation project(':car-apps-common')
+
+    implementation files(gradle.ext.lib_car_system_stubs)
+
+    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'org.mockito:mockito-android:3.12.4'
+    androidTestImplementation 'androidx.test:runner:1.4.0'
+    androidTestImplementation 'androidx.test:rules:1.4.0'
+    androidTestImplementation 'androidx.test:core:1.4.0'
+}
diff --git a/car-telephony-common/src/com/android/car/telephony/common/AsyncEntityLoader.java b/car-telephony-common/src/com/android/car/telephony/common/AsyncEntityLoader.java
new file mode 100644
index 0000000..ebfd08c
--- /dev/null
+++ b/car-telephony-common/src/com/android/car/telephony/common/AsyncEntityLoader.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2021 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.telephony.common;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.util.Log;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.core.content.ContentResolverCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.os.CancellationSignal;
+import androidx.core.os.OperationCanceledException;
+import androidx.loader.content.AsyncTaskLoader;
+import androidx.loader.content.Loader;
+
+/**
+ * An {@link AsyncTaskLoader} that queries database and convert {@link Cursor} to data entities.
+ *
+ * @param <T> type of data the cursor will be converted to.
+ */
+public class AsyncEntityLoader<T> extends AsyncTaskLoader<T> {
+
+    /**
+     * Caller of the {@link AsyncTaskLoader} will need to provide how to convert the loaded cursor
+     * to the data it consumes.
+     *
+     * @param <T> The type of data the cursor will be converted to.
+     */
+    public interface EntityConverter<T> {
+        /** Convert cursor to the desired type of data. This function is called on non-UI thread. */
+        @WorkerThread
+        T convertToEntity(Cursor cursor);
+    }
+
+    private static final String TAG = "CD.AsyncEntityLoader";
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final ForceLoadContentObserver mObserver;
+    private final QueryParam.Provider mQueryParamProvider;
+    private final EntityConverter<T> mEntityConverter;
+    private final Loader.OnLoadCompleteListener<T> mOnLoadCompleteListener;
+
+    private T mEntity;
+    private CancellationSignal mCancellationSignal;
+
+    public AsyncEntityLoader(
+            @NonNull Context context,
+            @NonNull QueryParam.Provider queryParamProvider,
+            @NonNull EntityConverter<T> entityConverter,
+            @NonNull Loader.OnLoadCompleteListener<T> onLoadCompleteListener) {
+        super(context);
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        mObserver = new ForceLoadContentObserver();
+        mQueryParamProvider = queryParamProvider;
+
+        mEntityConverter = entityConverter;
+        mOnLoadCompleteListener = onLoadCompleteListener;
+    }
+
+    @Nullable
+    @WorkerThread
+    @Override
+    public T loadInBackground() {
+        synchronized (this) {
+            if (isLoadInBackgroundCanceled()) {
+                throw new OperationCanceledException();
+            }
+            mCancellationSignal = new CancellationSignal();
+        }
+
+        Cursor cursor = null;
+        T entity = null;
+        try {
+            QueryParam queryParam = mQueryParamProvider.getQueryParam();
+
+            if (queryParam == null || ContextCompat.checkSelfPermission(mContext,
+                    queryParam.mPermission) != PackageManager.PERMISSION_GRANTED) {
+                return null;
+            }
+
+            mContentResolver.registerContentObserver(queryParam.mUri, false, mObserver);
+            cursor = ContentResolverCompat.query(mContentResolver,
+                    queryParam.mUri, queryParam.mProjection, queryParam.mSelection,
+                    queryParam.mSelectionArgs, queryParam.mOrderBy,
+                    mCancellationSignal);
+            if (cursor != null) {
+                // Ensure the cursor window is filled.
+                cursor.getCount();
+                entity = mEntityConverter.convertToEntity(cursor);
+                cursor.close();
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            synchronized (this) {
+                mCancellationSignal = null;
+            }
+        }
+        return entity;
+    }
+
+    @Override
+    public void cancelLoadInBackground() {
+        Log.d(TAG, "cancelLoadInBackground");
+        mContentResolver.unregisterContentObserver(mObserver);
+        synchronized (this) {
+            if (mCancellationSignal != null) {
+                mCancellationSignal.cancel();
+            }
+        }
+    }
+
+    @MainThread
+    @Override
+    public void deliverResult(@Nullable T entity) {
+        if (isReset()) {
+            return;
+        }
+
+        mEntity = entity;
+        if (isStarted()) {
+            Log.d(TAG, "deliverResult");
+            super.deliverResult(entity);
+        }
+    }
+
+    /**
+     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * will be called on the UI thread. If a previous load has been completed and is still valid
+     * the result may be passed to the callbacks immediately.
+     */
+    @MainThread
+    @Override
+    protected void onStartLoading() {
+        Log.d(TAG, "onStartLoading");
+        registerListener(0, mOnLoadCompleteListener);
+
+        if (mEntity != null) {
+            deliverResult(mEntity);
+        }
+        if (takeContentChanged() || mEntity == null) {
+            forceLoad();
+        }
+    }
+
+    @MainThread
+    @Override
+    protected void onStopLoading() {
+        Log.d(TAG, "onStopLoading");
+        // Attempt to cancel the current load task if possible.
+        cancelLoad();
+    }
+
+    @MainThread
+    @Override
+    protected void onReset() {
+        Log.d(TAG, "onReset");
+
+        // Ensure the loader is stopped.
+        onStopLoading();
+
+        mEntity = null;
+        unregisterListener(mOnLoadCompleteListener);
+    }
+}
diff --git a/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java b/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
index c4f6170..c2c2d81 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/AsyncQueryLiveData.java
@@ -21,15 +21,9 @@
 import android.database.Cursor;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.lifecycle.LiveData;
 
-import com.android.car.apps.common.log.L;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
 /**
  * Asynchronously queries a {@link ContentResolver} for a given query and observes the loaded data
  * for changes, reloading if necessary.
@@ -39,36 +33,23 @@
 public abstract class AsyncQueryLiveData<T> extends LiveData<T> {
 
     private static final String TAG = "CD.AsyncQueryLiveData";
-    private final ExecutorService mExecutorService;
-
-    private final ObservableAsyncQuery mObservableAsyncQuery;
-    private CursorRunnable mCurrentCursorRunnable;
-    private Future<?> mCurrentRunnableFuture;
+    private AsyncEntityLoader<T> mAsyncEntityLoader;
 
     public AsyncQueryLiveData(Context context, QueryParam.Provider provider) {
-        this(context, provider, WorkerExecutor.getInstance().getSingleThreadExecutor());
-    }
-
-    public AsyncQueryLiveData(Context context, QueryParam.Provider provider,
-            ExecutorService executorService) {
-        mObservableAsyncQuery = new ObservableAsyncQuery(context, provider, this::onCursorLoaded);
-        mExecutorService = executorService;
+        mAsyncEntityLoader = new AsyncEntityLoader<>(context, provider,
+                this::convertToEntity, (loader, entity) -> setValue(entity));
     }
 
     @Override
     protected void onActive() {
         super.onActive();
-        mObservableAsyncQuery.startQuery();
+        mAsyncEntityLoader.startLoading();
     }
 
     @Override
     protected void onInactive() {
         super.onInactive();
-        if (mCurrentCursorRunnable != null) {
-            mCurrentCursorRunnable.closeCursorIfNecessary();
-            mCurrentRunnableFuture.cancel(false);
-        }
-        mObservableAsyncQuery.stopQuery();
+        mAsyncEntityLoader.reset();
     }
 
     /**
@@ -76,44 +57,4 @@
      */
     @WorkerThread
     protected abstract T convertToEntity(@NonNull Cursor cursor);
-
-    private void onCursorLoaded(Cursor cursor) {
-        L.d(TAG, "onCursorLoaded: " + this);
-        if (mCurrentCursorRunnable != null) {
-            mCurrentCursorRunnable.closeCursorIfNecessary();
-            mCurrentRunnableFuture.cancel(false);
-        }
-        mCurrentCursorRunnable = new CursorRunnable(cursor);
-        mCurrentRunnableFuture = mExecutorService.submit(mCurrentCursorRunnable);
-    }
-
-    private class CursorRunnable implements Runnable {
-        private final Cursor mCursor;
-        private boolean mIsActive;
-
-        private CursorRunnable(@Nullable Cursor cursor) {
-            mCursor = cursor;
-            mIsActive = true;
-        }
-
-        @Override
-        public void run() {
-            // Bypass the workload to convert to entity and UI change triggered by post value if
-            // cursor is not current.
-            if (mIsActive) {
-                T entity = mCursor == null ? null : convertToEntity(mCursor);
-                if (mIsActive) {
-                    postValue(entity);
-                }
-            }
-            closeCursorIfNecessary();
-        }
-
-        public synchronized void closeCursorIfNecessary() {
-            if (!mIsActive && mCursor != null) {
-                mCursor.close();
-            }
-            mIsActive = false;
-        }
-    }
 }
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 25b40be..a9c6e75 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
@@ -40,7 +40,6 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.Executors;
 
 /**
  * A singleton statically accessible helper class which pre-loads contacts list into memory so that
@@ -123,7 +122,7 @@
                 .toQueryParam();
 
         mContactListAsyncQueryLiveData = new AsyncQueryLiveData<List<Contact>>(mContext,
-                QueryParam.of(contactListQueryParam), Executors.newSingleThreadExecutor()) {
+                QueryParam.of(contactListQueryParam)) {
             @Override
             protected List<Contact> convertToEntity(Cursor cursor) {
                 return onCursorLoaded(cursor);
diff --git a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java b/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
deleted file mode 100644
index 9f2f0cb..0000000
--- a/car-telephony-common/src/com/android/car/telephony/common/ObservableAsyncQuery.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.telephony.common;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.database.Cursor;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-
-import com.android.car.apps.common.log.L;
-
-/**
- * Asynchronously queries data and observes them. A new query will be triggered automatically if
- * data set have changed.
- */
-public class ObservableAsyncQuery {
-    private static final String TAG = "CD.ObservableAsyncQuery";
-
-    /**
-     * Called when query is finished.
-     */
-    public interface OnQueryFinishedListener {
-        /**
-         * Called when the query is finished loading. This callbacks will also be called if data
-         * changed.
-         *
-         * <p>Called on main thread.
-         */
-        @MainThread
-        void onQueryFinished(@Nullable Cursor cursor);
-    }
-
-    private Context mContext;
-    private AsyncQueryHandler mAsyncQueryHandler;
-    private QueryParam.Provider mQueryParamProvider;
-    private OnQueryFinishedListener mOnQueryFinishedListener;
-    private ContentObserver mContentObserver;
-    private ContentResolver mContentResolver;
-    private boolean mIsActive = false;
-    private int mToken;
-
-    /**
-     * @param queryParamProvider Supplies query arguments for the current query.
-     * @param listener           Listener which will be called when data is available.
-     */
-    public ObservableAsyncQuery(
-            @NonNull Context context,
-            @NonNull QueryParam.Provider queryParamProvider,
-            @NonNull OnQueryFinishedListener listener) {
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-        mAsyncQueryHandler = new AsyncQueryHandlerImpl(this, mContentResolver);
-        mContentObserver = new ContentObserver(mAsyncQueryHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                startQuery();
-            }
-        };
-        mQueryParamProvider = queryParamProvider;
-        mOnQueryFinishedListener = listener;
-        mToken = 0;
-    }
-
-    /**
-     * Starts the query and stops any pending query.
-     */
-    @MainThread
-    public void startQuery() {
-        L.d(TAG, "startQuery");
-        mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
-        mContentResolver.unregisterContentObserver(mContentObserver);
-
-        mToken++;
-        QueryParam queryParam = mQueryParamProvider.getQueryParam();
-        if (queryParam != null && ContextCompat.checkSelfPermission(mContext,
-                queryParam.mPermission) == PackageManager.PERMISSION_GRANTED) {
-            mAsyncQueryHandler.startQuery(
-                    mToken,
-                    null,
-                    queryParam.mUri,
-                    queryParam.mProjection,
-                    queryParam.mSelection,
-                    queryParam.mSelectionArgs,
-                    queryParam.mOrderBy);
-            mContentResolver.registerContentObserver(queryParam.mUri, false, mContentObserver);
-        } else {
-            mOnQueryFinishedListener.onQueryFinished(null);
-        }
-        mIsActive = true;
-
-    }
-
-    /**
-     * Stops any pending query and also stops listening on the data set change.
-     */
-    @MainThread
-    public void stopQuery() {
-        L.d(TAG, "stopQuery");
-        mIsActive = false;
-        mContentResolver.unregisterContentObserver(mContentObserver);
-        mAsyncQueryHandler.cancelOperation(mToken); // Cancel the query task.
-    }
-
-    private void onQueryComplete(int token, Object cookie, Cursor cursor) {
-        if (!mIsActive) {
-            return;
-        }
-        L.d(TAG, "onQueryComplete");
-        if (mOnQueryFinishedListener != null) {
-            mOnQueryFinishedListener.onQueryFinished(cursor);
-        }
-    }
-
-    private static class AsyncQueryHandlerImpl extends AsyncQueryHandler {
-        private ObservableAsyncQuery mQuery;
-
-        AsyncQueryHandlerImpl(ObservableAsyncQuery query, ContentResolver cr) {
-            super(cr);
-            mQuery = query;
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (token == mQuery.mToken) {
-                // The query result is the most up to date.
-                mQuery.onQueryComplete(token, cookie, cursor);
-            } else {
-                // Query canceled, close the cursor directly.
-                cursor.close();
-            }
-        }
-    }
-}
diff --git a/car-telephony-common/src/com/android/car/telephony/common/PhoneCallLog.java b/car-telephony-common/src/com/android/car/telephony/common/PhoneCallLog.java
index a1e25f2..927adf1 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/PhoneCallLog.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/PhoneCallLog.java
@@ -20,13 +20,18 @@
 import android.database.Cursor;
 import android.provider.CallLog;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 
+import com.google.common.base.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Entity class for call logs of a phone number. This call log may contains multiple call
@@ -35,6 +40,13 @@
 public class PhoneCallLog {
     private static final String TAG = "CD.PhoneCallLog";
 
+    @IntDef({TimeRange.TODAY, TimeRange.YESTERDAY, TimeRange.OLDER})
+    public @interface TimeRange {
+        int TODAY = 0;
+        int YESTERDAY = 1;
+        int OLDER = 2;
+    }
+
     /** Call log record. */
     public static class Record implements Comparable<Record> {
         private final long mCallEndTimestamp;
@@ -72,6 +84,7 @@
     private I18nPhoneNumberWrapper mI18nPhoneNumberWrapper;
     private String mAccountName;
     private List<Record> mCallRecords = new ArrayList<>();
+    private int mTimeRange;
 
     /**
      * Creates a {@link PhoneCallLog} from a {@link Cursor}.
@@ -90,6 +103,7 @@
                 phoneCallLog.mPhoneNumberString);
         Record record = new Record(cursor.getLong(dateColumn), cursor.getInt(callTypeColumn));
         phoneCallLog.mCallRecords.add(record);
+        phoneCallLog.mTimeRange = getTimeRange(record.getCallEndTimestamp());
         phoneCallLog.mAccountName = cursor.getString(accountNameColumn);
         return phoneCallLog;
     }
@@ -112,15 +126,10 @@
         return mId;
     }
 
-    /**
-     * Returns the last call end timestamp of this number. Returns -1 if there's no call log
-     * records.
-     */
+    /** Returns the last call end timestamp of this number. */
     public long getLastCallEndTimestamp() {
-        if (!mCallRecords.isEmpty()) {
-            return mCallRecords.get(0).getCallEndTimestamp();
-        }
-        return -1;
+        Preconditions.checkState(!mCallRecords.isEmpty(), "Unexpected empty call records");
+        return mCallRecords.get(0).getCallEndTimestamp();
     }
 
     /**
@@ -131,17 +140,28 @@
         return new ArrayList<>(mCallRecords);
     }
 
+    /** Returns the time range when the phone call was made. */
+    @TimeRange
+    public int getTimeRange() {
+        return mTimeRange;
+    }
+
     /**
      * Merges all call records with this call log's call records if they are representing the same
      * phone number.
+     *
+     * @param checkTimeRange if true, only merge the call records if they are in the same time range
      */
-    public boolean merge(@NonNull PhoneCallLog phoneCallLog) {
-        if (equals(phoneCallLog)) {
-            mCallRecords.addAll(phoneCallLog.mCallRecords);
-            Collections.sort(mCallRecords);
-            return true;
+    public boolean merge(@NonNull PhoneCallLog phoneCallLog, boolean checkTimeRange) {
+        if (!equals(phoneCallLog)) {
+            return false;
         }
-        return false;
+        if (checkTimeRange && mTimeRange != phoneCallLog.getTimeRange()) {
+            return false;
+        }
+        mCallRecords.addAll(phoneCallLog.mCallRecords);
+        Collections.sort(mCallRecords);
+        return true;
     }
 
     @Override
@@ -178,4 +198,17 @@
         sb.append(mAccountName);
         return sb.toString();
     }
+
+    @TimeRange
+    private static int getTimeRange(long callLogTime) {
+        if (DateUtils.isToday(callLogTime)) {
+            return TimeRange.TODAY;
+        }
+
+        if (DateUtils.isToday(callLogTime + TimeUnit.DAYS.toMillis(1))) {
+            return TimeRange.YESTERDAY;
+        }
+
+        return TimeRange.OLDER;
+    }
 }
diff --git a/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java b/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
index 2df3a26..c5fbeee 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/QueryParam.java
@@ -52,17 +52,17 @@
         QueryParam getQueryParam();
     }
 
-    /** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} as query param. */
     final Uri mUri;
-    /** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} as query param. */
     final String[] mProjection;
-    /** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} as query param. */
     final String mSelection;
-    /** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} as query param. */
     final String[] mSelectionArgs;
-    /** Used by {@link ObservableAsyncQuery#startQuery()} as query param. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} as query param. */
     final String mOrderBy;
-    /** Used by {@link ObservableAsyncQuery#startQuery()} to check query permission. */
+    /** Used by {@link AsyncEntityLoader#loadInBackground()} ()} to check query permission. */
     final String mPermission;
 
     public QueryParam(
diff --git a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
index 7f2be0a..b1a0d94 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/TelecomUtils.java
@@ -257,23 +257,13 @@
     @WorkerThread
     public static PhoneNumberInfo lookupNumberInBackground(Context context, String number) {
         if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
-                != PackageManager.PERMISSION_GRANTED) {
+                != PackageManager.PERMISSION_GRANTED
+                || TextUtils.isEmpty(number)) {
             String readableNumber = getReadableNumber(context, number);
             return new PhoneNumberInfo(number, readableNumber, readableNumber, null, null, null,
                     null);
         }
 
-        if (TextUtils.isEmpty(number)) {
-            return new PhoneNumberInfo(
-                    number,
-                    context.getString(R.string.unknown),
-                    context.getString(R.string.unknown),
-                    null,
-                    null,
-                    "",
-                    null);
-        }
-
         if (isVoicemailNumber(context, number)) {
             return new PhoneNumberInfo(
                     number,
@@ -372,10 +362,13 @@
                 lookupKey);
     }
 
-    private static String getReadableNumber(Context context, String number) {
+    /**
+     * Formats the phone number and presents as "Unknown" if empty.
+     */
+    public static String getReadableNumber(Context context, String number) {
         String readableNumber = getFormattedNumber(context, number);
 
-        if (readableNumber == null) {
+        if (TextUtils.isEmpty(readableNumber)) {
             readableNumber = context.getString(R.string.unknown);
         }
         return readableNumber;
diff --git a/car-telephony-common/tests/robotests/Android.bp b/car-telephony-common/tests/robotests/Android.bp
deleted file mode 100644
index 7f91c67..0000000
--- a/car-telephony-common/tests/robotests/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-//###########################################################
-// car-telephony-common just for Robolectric test target.   #
-//###########################################################
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
-    name: "CarTelephonyCommonForTesting",
-
-    platform_apis: true,
-
-    libs: ["android.car"],
-
-    privileged: true,
-
-    static_libs: ["car-telephony-common"],
-}
-
-//############################################################
-// car-telephony-common Robolectric test target.             #
-//############################################################
-android_robolectric_test {
-    name: "CarTelephonyCommonRoboTests",
-
-    srcs: ["src/**/*.java"],
-
-    java_resource_dirs: ["config"],
-
-    instrumentation_for: "CarTelephonyCommonForTesting",
-}
diff --git a/car-telephony-common/tests/robotests/config/robolectric.properties b/car-telephony-common/tests/robotests/config/robolectric.properties
deleted file mode 100644
index fc4f8ca..0000000
--- a/car-telephony-common/tests/robotests/config/robolectric.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (C) 2018 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.
-sdk=NEWEST_SDK
diff --git a/car-telephony-common/tests/unittests/Android.bp b/car-telephony-common/tests/unittests/Android.bp
index 466cd41..6398e3f 100644
--- a/car-telephony-common/tests/unittests/Android.bp
+++ b/car-telephony-common/tests/unittests/Android.bp
@@ -11,6 +11,7 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
+        "car-telephony-common",
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.runner",
@@ -18,6 +19,4 @@
         "truth-prebuilt",
         "mockito-target-minus-junit4",
     ],
-
-    instrumentation_for: "CarTelephonyLibTestsApp",
 }
diff --git a/car-telephony-common/tests/unittests/AndroidManifest.xml b/car-telephony-common/tests/unittests/AndroidManifest.xml
index 0cf6187..5a6390b 100644
--- a/car-telephony-common/tests/unittests/AndroidManifest.xml
+++ b/car-telephony-common/tests/unittests/AndroidManifest.xml
@@ -26,6 +26,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.car.telephony.common"
+                     android:targetPackage="com.android.car.telephony.common.unittests"
                      android:label="Car telephony common lib Unit Tests" />
 </manifest>
diff --git a/car-telephony-common/tests/robotests/src/com/android/car/telephony/common/ContactTest.java b/car-telephony-common/tests/unittests/src/com/android/car/telephony/common/ContactTest.java
similarity index 98%
rename from car-telephony-common/tests/robotests/src/com/android/car/telephony/common/ContactTest.java
rename to car-telephony-common/tests/unittests/src/com/android/car/telephony/common/ContactTest.java
index 4825895..81ba29a 100644
--- a/car-telephony-common/tests/robotests/src/com/android/car/telephony/common/ContactTest.java
+++ b/car-telephony-common/tests/unittests/src/com/android/car/telephony/common/ContactTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -27,18 +27,19 @@
 import android.provider.ContactsContract;
 import android.telephony.TelephonyManager;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class ContactTest {
 
     private static final int DISPLAY_NAME_COLUMN = 1;
diff --git a/car-theme-lib/res/values/themes_car.xml b/car-theme-lib/res/values/themes_car.xml
deleted file mode 100644
index 7fa9add..0000000
--- a/car-theme-lib/res/values/themes_car.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<!-- Copyright (C) 2018 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>
-    <!-- This theme exists only for defining new attrs relevant to Cars. It should not modify values
-     available from {@link android.R.style#Theme_DeviceDefault}. Overriding theme values is allowed
-     (e.g. {@link android.R.attr.dialogTheme ?android:dialogTheme} provided the referenced Theme
-     can be easily overlaid.-->
-    <style name="Theme.CarDefaultBase" parent="android:Theme.DeviceDefault">
-        <item name="android:dialogTheme">@style/Theme.CarDefault.Dialog</item>
-        <item name="android:alertDialogTheme">@style/Theme.CarDefault.Dialog.Alert</item>
-    </style>
-    <style name="Theme.CarDefaultBase.Dialog" parent="android:Theme.DeviceDefault.Dialog">
-        <item name="android:alertDialogTheme">@style/Theme.CarDefault.Dialog.Alert</item>
-    </style>
-    <style name="Theme.CarDefaultBase.Dialog.Alert" parent="android:Theme.DeviceDefault.Dialog.Alert">
-        <item name="android:alertDialogTheme">@style/Theme.CarDefault.Dialog.Alert</item>
-    </style>
-
-    <!-- Main theme for Car-based activities. This theme will automatically switch to darker colors
-     during night mode. -->
-    <style name="Theme.CarDefault" parent="Theme.CarDefaultBase"/>
-
-    <style name="Theme.CarDefault.Dialog" parent="Theme.CarDefaultBase.Dialog"/>
-
-    <style name="Theme.CarDefault.Dialog.Alert" parent="Theme.CarDefaultBase.Dialog.Alert"/>
-
-    <!-- Variant of the CarDefault theme with no action bar. -->
-    <style name="Theme.CarDefault.NoActionBar">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-    </style>
-
-    <!-- Variant of the CarDefault theme that has no title bar and fills the entire screen.  This
-     theme sets {@link android.R.attr#windowFullscreen} to true.  -->
-    <style name="Theme.CarDefault.NoActionBar.Fullscreen">
-        <item name="android:windowFullscreen">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-    </style>
-
-</resources>
diff --git a/car-ui-lib/Android.bp b/car-ui-lib/Android.bp
index 28ef55b..b6dddd8 100644
--- a/car-ui-lib/Android.bp
+++ b/car-ui-lib/Android.bp
@@ -17,6 +17,33 @@
 }
 
 android_library {
+    name: "car-ui-lib-resources",
+
+    sdk_version: "current",
+    min_sdk_version: "28",
+    target_sdk_version: "30",
+
+    manifest: "car-ui-lib/src/main/AndroidManifest.xml",
+    resource_dirs: [
+        "car-ui-lib/src/main/res",
+        "car-ui-lib/src/main/res-overlayable",
+        "car-ui-lib/src/main/res-private"
+    ],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx-constraintlayout_constraintlayout",
+        "androidx.preference_preference",
+        "androidx.recyclerview_recyclerview",
+        "car-rotary-lib-resources",
+    ],
+    apex_available: [
+        "com.android.permission",
+        "//apex_available:platform",
+    ],
+}
+
+android_library {
     name: "car-ui-lib",
 
     sdk_version: "current",
@@ -25,11 +52,6 @@
 
     manifest: "car-ui-lib/src/main/AndroidManifest.xml",
     srcs: ["car-ui-lib/src/main/java/**/*.java"],
-    resource_dirs: [
-        "car-ui-lib/src/main/res",
-        "car-ui-lib/src/main/res-overlayable",
-        "car-ui-lib/src/main/res-private"
-    ],
     optimize: {
         proguard_flags_files: [
             "car-ui-lib/proguard-rules.pro",
@@ -48,6 +70,7 @@
         "androidx.recyclerview_recyclerview",
         "androidx-constraintlayout_constraintlayout-solver",
         "androidx.asynclayoutinflater_asynclayoutinflater",
+        "car-ui-lib-resources",
         "car-rotary-lib",
     ],
 }
@@ -73,6 +96,37 @@
     ],
 }
 
+android_library {
+    name: "car-ui-lib-testing",
+
+    sdk_version: "current",
+    min_sdk_version: "28",
+    target_sdk_version: "30",
+
+    manifest: "car-ui-lib/src/androidTest/AndroidManifest.xml",
+    srcs: [
+        "car-ui-lib/src/androidTest/java/**/actions/*.java",
+        "car-ui-lib/src/androidTest/java/**/matchers/*.java",
+    ],
+    resource_dirs: [
+        "car-ui-lib/src/androidTest/res",
+    ],
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "car-ui-lib",
+        "androidx.test.rules",
+        "androidx.test.espresso.core",
+        "androidx.test.espresso.contrib",
+        "androidx.test.ext.junit",
+    ],
+    apex_available: [
+        "com.android.permission",
+        "//apex_available:platform",
+    ],
+}
+
 android_test {
     name: "CarUILibUnitTests",
     certificate: "platform",
@@ -101,7 +155,7 @@
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
     ],
-
+    required: ["car-ui-lib-plugin"],
     platform_apis: true,
     test_suites: ["device-tests"],
 }
diff --git a/car-ui-lib/README.md b/car-ui-lib/README.md
index 8cb8c39..155f441 100644
--- a/car-ui-lib/README.md
+++ b/car-ui-lib/README.md
@@ -35,11 +35,11 @@
 
 If it launches a LeakCanary activity instead of PaintBooth, either exit LeakCanary and launch PaintBooth as normal through the car's launcher, or click on the PaintBooth module > Edit configurations > Change "Launch: Default Activity" to "Specified Activity", and enter `com.android.car.ui.paintbooth.MainActivity`.
 
-### Building and running the shared library
+### Building and running the CarUi Plugin
 
-Setting up the shared library is mostly the same as setting up paintbooth. However, when you attempt to install the shared library, Android Studio will complain it can't launch any activity (despite the installation succeeding), and your changes won't properly show up. To fix these issues, edit the shared library configuration, change the "Launch:" option to launch nothing, and check the "Always install with package manager (disables deployment optimizations on Android 11 and higher)" button. This checkbox shouldn't be required after b/188220380 is fixed.
+Setting up the plugin is mostly the same as setting up paintbooth. However, when you attempt to install the plugin, Android Studio will complain it can't launch any activity (despite the installation succeeding), and your changes won't properly show up. To fix these issues, edit the plugin configuration, change the "Launch:" option to launch nothing, and check the "Always install with package manager (disables deployment optimizations on Android 11 and higher)" button. This checkbox shouldn't be required after b/188220380 is fixed.
 
-![Shared library setup](documentation/images/shared_library_setup.png)
+![Plugin setup](documentation/images/plugin_setup.png)
 
 ### Running tests
 
diff --git a/car-ui-lib/build.gradle b/car-ui-lib/build.gradle
index da2a961..4ee7a04 100644
--- a/car-ui-lib/build.gradle
+++ b/car-ui-lib/build.gradle
@@ -28,7 +28,7 @@
         // studio and have it work. Sometimes android studio's built in gradle
         // wrapper seems to lag behind the version required by the android
         // plugin.
-        classpath 'com.android.tools.build:gradle:4.1.3'
+        classpath 'com.android.tools.build:gradle:7.0.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/car-ui-lib/car-rotary-lib/.gitignore b/car-ui-lib/car-rotary-lib/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/car-ui-lib/car-rotary-lib/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/car-ui-lib/car-rotary-lib/Android.bp b/car-ui-lib/car-rotary-lib/Android.bp
index f701cf5..cafe6d4 100644
--- a/car-ui-lib/car-rotary-lib/Android.bp
+++ b/car-ui-lib/car-rotary-lib/Android.bp
@@ -17,6 +17,27 @@
 }
 
 android_library {
+    name: "car-rotary-lib-resources",
+
+    sdk_version: "current",
+    min_sdk_version: "28",
+    target_sdk_version: "30",
+
+    manifest: "src/main/AndroidManifest.xml",
+    resource_dirs: [
+        "src/main/res",
+        "src/main/res-overlayable",
+    ],
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+    ],
+    apex_available: [
+        "com.android.permission",
+        "//apex_available:platform",
+    ],
+}
+
+android_library {
     name: "car-rotary-lib",
 
     sdk_version: "current",
@@ -25,16 +46,14 @@
 
     manifest: "src/main/AndroidManifest.xml",
     srcs: ["src/main/java/**/*.java"],
-    resource_dirs: [
-        "src/main/res",
-        "src/main/res-overlayable",
-    ],
     optimize: {
         enabled: false,
     },
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx-constraintlayout_constraintlayout",
         "androidx.recyclerview_recyclerview",
+        "car-rotary-lib-resources",
     ],
     apex_available: [
         "com.android.permission",
diff --git a/car-ui-lib/car-rotary-lib/build.gradle b/car-ui-lib/car-rotary-lib/build.gradle
index 0784ed6..2ffbf22 100644
--- a/car-ui-lib/car-rotary-lib/build.gradle
+++ b/car-ui-lib/car-rotary-lib/build.gradle
@@ -55,6 +55,10 @@
         }
     }
 
+    buildFeatures {
+        buildConfig = false
+    }
+
     // This is the gradle equivalent of the libs: ["android.car"] in the Android.bp
     useLibrary 'android.car'
 
@@ -65,21 +69,22 @@
 
 dependencies {
     api 'androidx.annotation:annotation:1.2.0'
-    api 'androidx.recyclerview:recyclerview:1.2.0'
+    api 'androidx.constraintlayout:constraintlayout:2.0.4'
+    api 'androidx.recyclerview:recyclerview:1.2.1'
 
     // The tests use a CarUiRecyclerView
     androidTestImplementation project(':car-ui-lib')
     androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
     androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
-    androidTestImplementation "com.google.truth:truth:1.1.2"
-    androidTestImplementation "androidx.test.ext:junit:1.1.2"
+    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
     androidTestImplementation "org.mockito:mockito-core:2.19.0"
-    androidTestImplementation 'androidx.test:runner:1.3.0'
-    androidTestImplementation 'androidx.test:rules:1.3.0'
+    androidTestImplementation 'androidx.test:runner:1.4.0'
+    androidTestImplementation 'androidx.test:rules:1.4.0'
     // This is needed to be able to spy certain classes with Mockito
     // It's major/minor version must match Mockito's.
-    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.19.0'
+    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.1'
     // Required for instrumented tests
     androidTestImplementation 'com.android.support:support-annotations:28.0.0'
 }
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/AndroidManifest.xml b/car-ui-lib/car-rotary-lib/src/androidTest/AndroidManifest.xml
index 977e4be..c228c37 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/AndroidManifest.xml
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/AndroidManifest.xml
@@ -19,10 +19,14 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.car.rotary.test">
     <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="com.android.car.ui.FocusAreaTestActivity" />
-        <activity android:name="com.android.car.ui.FocusParkingViewTestActivity" />
-        <activity android:name="com.android.car.ui.utils.ViewUtilsTestActivity" />
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.car.ui.utils.DirectManipulationHelperTestActivity"
+                  android:theme="@style/Theme.CarUi.NoToolbar"/>
+        <activity android:name="com.android.car.ui.FocusAreaTestActivity"/>
+        <activity android:name="com.android.car.ui.FocusParkingViewTestActivity"
+            android:theme="@style/Theme.CarUi.NoToolbar"/>
+        <activity android:name="com.android.car.ui.utils.ViewUtilsTestActivity"
+            android:theme="@style/Theme.CarUi.NoToolbar"/>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.car.rotary.test"
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
index 400c946..b9f8125 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusAreaTest.java
@@ -111,8 +111,8 @@
         CountDownLatch latch = new CountDownLatch(1);
         mView1.post(() -> {
             mView1.requestFocus();
-            mFocusArea1.enableForegroundHighlight();
-            mFocusArea2.enableForegroundHighlight();
+            mFocusArea1.getHelper().enableForegroundHighlight();
+            mFocusArea2.getHelper().enableForegroundHighlight();
             mFocusArea1.setOnDrawCalled(false);
             mFocusArea1.setDrawCalled(false);
             mFocusArea2.setOnDrawCalled(false);
@@ -331,7 +331,7 @@
                 new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
         CountDownLatch latch1 = new CountDownLatch(1);
         mButton1.post(() -> {
-            mFocusArea1.setRotaryCache(cache);
+            mFocusArea1.getHelper().setRotaryCache(cache);
             mButton1.requestFocus();
             mButton1.post(() -> latch1.countDown());
 
@@ -363,7 +363,7 @@
         RotaryCache cache = new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
         CountDownLatch latch1 = new CountDownLatch(1);
         mButton1.post(() -> {
-            mFocusArea1.setRotaryCache(cache);
+            mFocusArea1.getHelper().setRotaryCache(cache);
             mButton1.requestFocus();
             mButton1.post(() -> latch1.countDown());
         });
@@ -427,10 +427,10 @@
         mButton1.post(() -> {
             RotaryCache cache1 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea1.setRotaryCache(cache1);
+            mFocusArea1.getHelper().setRotaryCache(cache1);
             RotaryCache cache2 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache2);
+            mFocusArea2.getHelper().setRotaryCache(cache2);
 
             // Focus on the second view in mFocusArea1.
             mButton1.requestFocus();
@@ -482,10 +482,10 @@
             // Disabled FocusCache but enabled FocusAreaCache.
             RotaryCache cache1 =
                     new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea1.setRotaryCache(cache1);
+            mFocusArea1.getHelper().setRotaryCache(cache1);
             RotaryCache cache2 =
                     new RotaryCache(CACHE_TYPE_DISABLED, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache2);
+            mFocusArea2.getHelper().setRotaryCache(cache2);
 
             // Focus on the second view in mFocusArea1.
             mButton1.requestFocus();
@@ -525,10 +525,10 @@
             // Enabled FocusCache but disabled FocusAreaCache.
             RotaryCache cache1 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
-            mFocusArea1.setRotaryCache(cache1);
+            mFocusArea1.getHelper().setRotaryCache(cache1);
             RotaryCache cache2 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_DISABLED, 0);
-            mFocusArea2.setRotaryCache(cache2);
+            mFocusArea2.getHelper().setRotaryCache(cache2);
 
             // Focus on the second view in mFocusArea1, then nudge to mFocusArea2.
             mButton1.requestFocus();
@@ -616,12 +616,62 @@
     }
 
     @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_specifiedTarget2()
+            throws Exception {
+        // Nudge to specified FocusArea.
+        CountDownLatch latch1 = new CountDownLatch(1);
+        mView4.post(() -> {
+            mView4.requestFocus();
+            mView4.post(() -> latch1.countDown());
+        });
+        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        assertThat(mView4.isFocused()).isTrue();
+
+        // Clear the attribute specified in the XML file.
+        CountDownLatch latch2 = new CountDownLatch(1);
+        Bundle arguments = new Bundle();
+        mFocusArea4.post(() -> {
+            mFocusArea4.setNudgeTargetFocusArea(FOCUS_LEFT, null);
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+            mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            mFocusArea4.post(() -> latch2.countDown());
+        });
+        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        assertThat(mView4.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testPerformAccessibilityAction_actionNudgeToAnotherFocusArea_specifiedTarget3()
+            throws Exception {
+        // Nudge to specified FocusArea.
+        CountDownLatch latch1 = new CountDownLatch(1);
+        mView4.post(() -> {
+            mView4.requestFocus();
+            mView4.post(() -> latch1.countDown());
+        });
+        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        assertThat(mView4.isFocused()).isTrue();
+
+        // Set the attribute programmatically.
+        CountDownLatch latch2 = new CountDownLatch(1);
+        Bundle arguments = new Bundle();
+        mFocusArea4.post(() -> {
+            mFocusArea4.setNudgeTargetFocusArea(FOCUS_LEFT, mFocusArea1);
+            arguments.putInt(NUDGE_DIRECTION, FOCUS_LEFT);
+            mFocusArea4.performAccessibilityAction(ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA, arguments);
+            mFocusArea4.post(() -> latch2.countDown());
+        });
+        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        assertThat(mView1.isFocused()).isTrue();
+    }
+
+    @Test
     public void testDefaultFocusOverridesHistory_override() throws Exception {
         CountDownLatch latch1 = new CountDownLatch(1);
         mView2.post(() -> {
             RotaryCache cache =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache);
+            mFocusArea2.getHelper().setRotaryCache(cache);
             mFocusArea2.setDefaultFocusOverridesHistory(true);
             mView2.requestFocus();
             mView2.post(() -> latch1.countDown());
@@ -654,7 +704,7 @@
         mView2.post(() -> {
             RotaryCache cache =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache);
+            mFocusArea2.getHelper().setRotaryCache(cache);
             mView2.requestFocus();
             mView2.post(() -> latch1.countDown());
         });
@@ -685,12 +735,12 @@
         mView1.post(() -> {
             RotaryCache cache1 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea1.setRotaryCache(cache1);
-            mFocusArea1.setClearFocusAreaHistoryWhenRotating(true);
+            mFocusArea1.getHelper().setRotaryCache(cache1);
+            mFocusArea1.getHelper().setClearFocusAreaHistoryWhenRotating(true);
             RotaryCache cache2 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache2);
-            mFocusArea2.setClearFocusAreaHistoryWhenRotating(true);
+            mFocusArea2.getHelper().setRotaryCache(cache2);
+            mFocusArea2.getHelper().setClearFocusAreaHistoryWhenRotating(true);
             mView1.requestFocus();
             mView1.post(() -> latch1.countDown());
         });
@@ -735,12 +785,12 @@
         mView1.post(() -> {
             RotaryCache cache1 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea1.setRotaryCache(cache1);
-            mFocusArea1.setClearFocusAreaHistoryWhenRotating(false);
+            mFocusArea1.getHelper().setRotaryCache(cache1);
+            mFocusArea1.getHelper().setClearFocusAreaHistoryWhenRotating(false);
             RotaryCache cache2 =
                     new RotaryCache(CACHE_TYPE_NEVER_EXPIRE, 0, CACHE_TYPE_NEVER_EXPIRE, 0);
-            mFocusArea2.setRotaryCache(cache2);
-            mFocusArea2.setClearFocusAreaHistoryWhenRotating(false);
+            mFocusArea2.getHelper().setRotaryCache(cache2);
+            mFocusArea2.getHelper().setClearFocusAreaHistoryWhenRotating(false);
             mView1.requestFocus();
             mView1.post(() -> latch1.countDown());
         });
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
index 8076481..04ad00a 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTest.java
@@ -18,6 +18,11 @@
 
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.car.ui.actions.ViewActions.waitForView;
 import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
 import static com.android.car.ui.utils.ViewUtils.setRotaryScrollEnabled;
 
@@ -25,26 +30,20 @@
 
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.car.rotary.test.R;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.utils.TestUtils;
+import com.android.car.ui.utils.ViewUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 /** Unit test for {@link FocusParkingView} not in touch mode. */
-// TODO(b/187553946): Improve this test.
 public class FocusParkingViewTest {
 
-    private static final long WAIT_TIME_MS = 3000;
     private static final int NUM_ITEMS = 40;
 
     @Rule
@@ -52,26 +51,31 @@
             new ActivityTestRule<>(FocusParkingViewTestActivity.class);
 
     private FocusParkingViewTestActivity mActivity;
-    private FocusParkingView mFpv;
     private ViewGroup mParent1;
     private View mView1;
     private View mFocusedByDefault;
-    private RecyclerView mList;
+    private CarUiRecyclerView mList;
+    private View mRoot;
+    private FocusParkingView mFpv;
 
     @Before
     public void setUp() {
         mActivity = mActivityRule.getActivity();
-        mFpv = mActivity.findViewById(R.id.fpv);
         mParent1 = mActivity.findViewById(R.id.parent1);
         mView1 = mActivity.findViewById(R.id.view1);
         mFocusedByDefault = mActivity.findViewById(R.id.focused_by_default);
         mList = mActivity.findViewById(R.id.list);
+        mRoot = mView1.getRootView();
+        // Since FocusParkingViewTestActivity uses Theme.CarUi.NoToolbar, a FocusParkingView has
+        // been added to the view tree automatically.
+        mFpv = ViewUtils.findFocusParkingView(mRoot);
 
-        mList.post(() -> {
-            mList.setLayoutManager(new LinearLayoutManager(mActivity));
+        mRoot.post(() -> {
             mList.setAdapter(new TestAdapter(NUM_ITEMS));
-            setRotaryScrollEnabled(mList, /* isVertical= */ true);
+            setRotaryScrollEnabled(mList.getView(), /* isVertical= */ true);
         });
+        // If we don't wait for the recyclerview items to show up, some of the tests flake
+        onView(isRoot()).perform(waitForView(withText("Item 0"), 500));
     }
 
     @Test
@@ -82,205 +86,81 @@
 
     @Test
     public void testRequestFocus_focusOnDefaultFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
-            mFpv.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mFpv.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.requestFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mFocusedByDefault.isFocused()).isTrue();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */ mFpv,
+                /* viewToGetFocus= */mFocusedByDefault);
     }
 
     @Test
     public void testRequestFocus_doNothing() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.requestFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */
+                mView1, /* viewToGetFocus= */ mView1);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */ mFpv,
+                /* viewToGetFocus= */mView1);
     }
 
     @Test
     public void testRestoreDefaultFocus_focusOnDefaultFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
-            mFpv.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mFpv.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.restoreDefaultFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        TestUtils.accept(mFpv, v -> v.restoreDefaultFocus());
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreDefaultFocus_doNothing() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.restoreDefaultFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mFpv, v -> v.restoreDefaultFocus());
         assertThat(mView1.isFocused()).isTrue();
     }
 
     @Test
     public void testOnWindowFocusChanged_loseFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.onWindowFocusChanged(false);
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mFpv, v -> v.onWindowFocusChanged(false));
         assertThat(mFpv.isFocused()).isTrue();
     }
 
     @Test
     public void testOnWindowFocusChanged_focusOnDefaultFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
-            mFpv.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mFpv.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.onWindowFocusChanged(true);
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        TestUtils.accept(mFpv, v -> v.onWindowFocusChanged(true));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testPerformAccessibilityAction_actionRestoreDefaultFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
-            mFpv.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mFpv.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null);
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        TestUtils.accept(mFpv,
+                v -> v.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testPerformAccessibilityAction_doNothing() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null);
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mFpv,
+                v -> v.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS, null));
         assertThat(mView1.isFocused()).isTrue();
     }
 
     @Test
     public void testPerformAccessibilityAction_actionFocus() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.performAccessibilityAction(ACTION_FOCUS, null);
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mFpv, v -> v.performAccessibilityAction(ACTION_FOCUS, null));
         assertThat(mFpv.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreFocusInRoot_recyclerViewItemRemoved() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mList.post(() -> mList.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        mList.post(() -> latch1.countDown());
-                    }
-                })
-        );
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-
         View firstItem = mList.getLayoutManager().findViewByPosition(0);
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mList.post(() -> {
-            firstItem.requestFocus();
-            mList.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(firstItem.isFocused()).isTrue();
-
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */
+                firstItem, /* viewToGetFocus= */ firstItem);
         ViewGroup parent = (ViewGroup) firstItem.getParent();
-        CountDownLatch latch3 = new CountDownLatch(1);
-        parent.post(() -> {
-            parent.removeView(firstItem);
-            parent.post(() -> latch3.countDown());
-        });
-        latch3.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.accept(parent, v -> parent.removeView(firstItem));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
@@ -315,118 +195,52 @@
 
     @Test
     public void testRestoreFocusInRoot_focusedViewRemoved() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mView1.post(() -> {
-            ViewGroup parent = (ViewGroup) mView1.getParent();
-            parent.removeView(mView1);
-            mView1.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        ViewGroup parent = (ViewGroup) mView1.getParent();
+        TestUtils.accept(parent, v -> parent.removeView(mView1));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreFocusInRoot_focusedViewDisabled() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.setEnabled(false);
-            mView1.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mView1, v -> v.setEnabled(false));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreFocusInRoot_focusedViewBecomesInvisible() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.setVisibility(View.INVISIBLE);
-            mView1.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mView1, v -> v.setVisibility(View.INVISIBLE));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreFocusInRoot_focusedViewParentBecomesInvisible() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mParent1.post(() -> {
-            mParent1.setVisibility(View.INVISIBLE);
-            mParent1.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        TestUtils.accept(mParent1, v -> v.setVisibility(View.INVISIBLE));
         assertThat(mFocusedByDefault.isFocused()).isTrue();
     }
 
     @Test
     public void testRequestFocus_focusesFpvWhenShouldRestoreFocusIsFalse() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.setShouldRestoreFocus(false);
-            mFpv.requestFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        mFpv.setShouldRestoreFocus(false);
+        TestUtils.accept(mFpv, v -> v.requestFocus());
         assertThat(mFpv.isFocused()).isTrue();
     }
 
     @Test
     public void testRestoreDefaultFocus_focusesFpvWhenShouldRestoreFocusIsFalse() throws Exception {
-        CountDownLatch latch1 = new CountDownLatch(1);
-        mView1.post(() -> {
-            mView1.requestFocus();
-            mView1.post(() -> latch1.countDown());
-        });
-        latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        assertThat(mView1.isFocused()).isTrue();
-
-        CountDownLatch latch2 = new CountDownLatch(1);
-        mFpv.post(() -> {
-            mFpv.setShouldRestoreFocus(false);
-            mFpv.restoreDefaultFocus();
-            mFpv.post(() -> latch2.countDown());
-        });
-        latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+        TestUtils.requestFocusAndAssertFocused(/* viewToRequestFocus= */mView1,
+                /* viewToGetFocus= */ mView1);
+        mFpv.setShouldRestoreFocus(false);
+        TestUtils.accept(mFpv, v -> v.restoreDefaultFocus());
         assertThat(mFpv.isFocused()).isTrue();
     }
 }
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
index efa2e90..1f89940 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/FocusParkingViewTouchModeTest.java
@@ -26,6 +26,8 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.car.rotary.test.R;
+import com.android.car.ui.utils.TestUtils;
+import com.android.car.ui.utils.ViewUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -44,46 +46,42 @@
     @Before
     public void setUp() {
         FocusParkingViewTestActivity activity = mActivityRule.getActivity();
-        mFpv = activity.findViewById(R.id.fpv);
+        // Since FocusParkingViewTestActivity uses Theme.CarUi.NoToolbar, a FocusParkingView has
+        // been added to the view tree automatically.
+        View view1 = activity.findViewById(R.id.view1);
+        View root = view1.getRootView();
+        mFpv = ViewUtils.findFocusParkingView(root);
     }
 
     @Test
-    public void testRestoreDefaultFocus_doesNothing() {
-        mFpv.post(() -> {
-            assertThat(mFpv.getRootView().findFocus()).isNull();
+    public void testRestoreDefaultFocus_doesNothing() throws InterruptedException {
+        assertThat(mFpv.getRootView().findFocus()).isNull();
 
-            boolean result = mFpv.restoreDefaultFocus();
-
-            assertWithMessage("restoreDefaultFocus returned").that(result).isFalse();
-            assertWithMessage("No view should be focused")
-                    .that(mFpv.getRootView().findFocus()).isNull();
-        });
+        boolean success = TestUtils.test(mFpv, v -> v.restoreDefaultFocus());
+        assertWithMessage("restoreDefaultFocus returned").that(success).isFalse();
+        assertWithMessage("No view should be focused")
+                .that(mFpv.getRootView().findFocus()).isNull();
     }
 
     @Test
-    public void testRequestFocus_doesNothing() {
-        mFpv.post(() -> {
-            assertThat(mFpv.getRootView().findFocus()).isNull();
-
-            boolean result = mFpv.requestFocus(View.FOCUS_DOWN, /* previouslyFocusedRect= */ null);
-
-            assertWithMessage("requestFocus returned").that(result).isFalse();
-            assertWithMessage("No view should be focused")
-                    .that(mFpv.getRootView().findFocus()).isNull();
-        });
+    public void testRequestFocus_doesNothing() throws InterruptedException {
+        assertThat(mFpv.getRootView().findFocus()).isNull();
+        boolean success = TestUtils.test(mFpv,
+                v -> v.requestFocus(View.FOCUS_DOWN, /* previouslyFocusedRect= */ null));
+        assertWithMessage("requestFocus returned").that(success).isFalse();
+        assertWithMessage("No view should be focused")
+                .that(mFpv.getRootView().findFocus()).isNull();
     }
 
     @Test
-    public void testPerformActionRestoreDefaultFocus_exitsTouchMode() {
-        mFpv.post(() -> {
-            assertThat(mFpv.getRootView().findFocus()).isNull();
-
-            boolean result = mFpv.performAccessibilityAction(
-                    ACTION_RESTORE_DEFAULT_FOCUS, /* arguments= */ null);
-
-            assertWithMessage("performAccessibilityAction returned").that(result).isTrue();
-            assertWithMessage("A view should be focused")
-                    .that(mFpv.getRootView().findFocus()).isNotNull();
-        });
+    public void testPerformActionRestoreDefaultFocus_exitsTouchMode() throws InterruptedException {
+        assertThat(mFpv.getRootView().findFocus()).isNull();
+        boolean success = TestUtils.test(mFpv,
+                v -> v.performAccessibilityAction(ACTION_RESTORE_DEFAULT_FOCUS,
+                        /* arguments= */ null));
+        assertWithMessage("performAccessibilityAction returned")
+                .that(success).isTrue();
+        assertWithMessage("A view should be focused")
+                .that(mFpv.getRootView().findFocus()).isNotNull();
     }
 }
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
index b1b9b59..2b92a34 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/RotaryCacheTest.java
@@ -77,7 +77,7 @@
     public void testGetCachedFocusArea_inTheCache() {
         int direction = View.FOCUS_LEFT;
         mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
-        FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mValidTime);
+        IFocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mValidTime);
         assertThat(focusArea).isEqualTo(mFocusArea);
     }
 
@@ -86,7 +86,7 @@
         int direction = View.FOCUS_LEFT;
         mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
 
-        FocusArea focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_RIGHT, mValidTime);
+        IFocusArea focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_RIGHT, mValidTime);
         assertThat(focusArea).isNull();
         focusArea = mRotaryCache.getCachedFocusArea(View.FOCUS_UP, mValidTime);
         assertThat(focusArea).isNull();
@@ -96,7 +96,7 @@
     public void testGetCachedFocusArea_expiredCache() {
         int direction = View.FOCUS_LEFT;
         mRotaryCache.saveFocusArea(direction, mFocusArea, 0);
-        FocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mExpiredTime);
+        IFocusArea focusArea = mRotaryCache.getCachedFocusArea(direction, mExpiredTime);
         assertThat(focusArea).isNull();
     }
 
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTest.java
new file mode 100644
index 0000000..32d6b13
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.ui.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+/** Unit tests for {@link DirectManipulationHelper}. */
+public class DirectManipulationHelperTest {
+    @Rule
+    public ActivityTestRule<DirectManipulationHelperTestActivity> mActivityRule =
+            new ActivityTestRule<>(DirectManipulationHelperTestActivity.class);
+
+    private DirectManipulationHelperTestActivity mActivity;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private View mView;
+
+    @Captor
+    ArgumentCaptor<AccessibilityEvent> mAccessibilityEventCaptor;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        initMocks(this);
+    }
+
+    @Test
+    public void testEnableDirectManipulationMode_fail() {
+        when(mView.getContext()).thenReturn(mContext);
+        when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE)).thenReturn(
+                mAccessibilityManager);
+        when(mAccessibilityManager.isEnabled()).thenReturn(false);
+
+        boolean sent = DirectManipulationHelper.enableDirectManipulationMode(mView, true);
+
+        assertThat(sent).isFalse();
+        verify(mAccessibilityManager, never()).sendAccessibilityEvent(any());
+    }
+
+    @Test
+    public void testEnableDirectManipulationMode_enable() {
+        when(mView.getContext()).thenReturn(mContext);
+        when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE)).thenReturn(
+                mAccessibilityManager);
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+        boolean sent = DirectManipulationHelper.enableDirectManipulationMode(mView, true);
+
+        assertThat(sent).isTrue();
+        verify(mAccessibilityManager).sendAccessibilityEvent(mAccessibilityEventCaptor.capture());
+        AccessibilityEvent capturedEvent = mAccessibilityEventCaptor.getValue();
+        assertThat(capturedEvent.getClassName()).isEqualTo(
+                DirectManipulationHelper.DIRECT_MANIPULATION);
+        assertThat(capturedEvent.getEventType()).isEqualTo(
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+    }
+
+    @Test
+    public void testEnableDirectManipulationMode_disable() {
+        when(mView.getContext()).thenReturn(mContext);
+        when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE)).thenReturn(
+                mAccessibilityManager);
+        when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+        boolean sent = DirectManipulationHelper.enableDirectManipulationMode(mView, false);
+
+        assertThat(sent).isTrue();
+        verify(mAccessibilityManager).sendAccessibilityEvent(mAccessibilityEventCaptor.capture());
+        AccessibilityEvent capturedEvent = mAccessibilityEventCaptor.getValue();
+        assertThat(capturedEvent.getClassName()).isEqualTo(
+                DirectManipulationHelper.DIRECT_MANIPULATION);
+        assertThat(capturedEvent.getEventType()).isEqualTo(
+                AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+    }
+
+    @Test
+    public void testIsDirectManipulation() {
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        assertThat(DirectManipulationHelper.isDirectManipulation(event)).isFalse();
+
+        event.setClassName(DirectManipulationHelper.DIRECT_MANIPULATION);
+        assertThat(DirectManipulationHelper.isDirectManipulation(event)).isTrue();
+    }
+
+    @Test
+    public void testSupportRotateDirectly() {
+        AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+        assertThat(DirectManipulationHelper.supportRotateDirectly(node)).isFalse();
+
+        node.setContentDescription("normal content description");
+        assertThat(DirectManipulationHelper.supportRotateDirectly(node)).isFalse();
+
+        node.setContentDescription(DirectManipulationHelper.DIRECT_MANIPULATION);
+        assertThat(DirectManipulationHelper.supportRotateDirectly(node)).isTrue();
+    }
+
+    @Test
+    public void testSetSupportsRotateDirectly() {
+        View view = mActivity.findViewById(R.id.view);
+        assertThat(view.getContentDescription()).isNull();
+
+        DirectManipulationHelper.setSupportsRotateDirectly(view, true);
+        assertThat(view.getContentDescription()).isEqualTo(
+                DirectManipulationHelper.DIRECT_MANIPULATION);
+
+        DirectManipulationHelper.setSupportsRotateDirectly(view, false);
+        assertThat(view.getContentDescription()).isNull();
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTestActivity.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTestActivity.java
new file mode 100644
index 0000000..7f0c639
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/DirectManipulationHelperTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.ui.utils;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.car.rotary.test.R;
+
+/** An activity used for testing {@link DirectManipulationHelper}. */
+public class DirectManipulationHelperTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.direct_manipulation_helper_test_activity);
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/TestUtils.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/TestUtils.java
new file mode 100644
index 0000000..1898e40
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/TestUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.ui.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.car.ui.FocusParkingView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/** Utility class for helpful methods used in tests. */
+public final class TestUtils {
+    private static final long WAIT_TIME_MS = 3000;
+
+    /**
+     * Performs the {@code consumer} on the UI thread.
+     * <p>
+     * The given {@code view} must be attached to window, otherwise the Runnable will never run.
+     */
+    public static void accept(View view, Consumer<View> consumer)
+            throws InterruptedException {
+        if (!view.isAttachedToWindow()) {
+            throw new RuntimeException("Don't post Runnable on the view because it's detached from"
+                    + " window: " + view);
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+        view.post(() -> {
+            consumer.accept(view);
+            latch.countDown();
+        });
+        assertWithMessage("Timeout")
+                .that(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isTrue();
+    }
+
+    /**
+     * Evaluates the {@code predicate} on the UI thread and returns the result.
+     * <p>
+     * The given {@code view} must be attached to window, otherwise the Runnable will never run.
+     */
+    public static boolean test(View view, Predicate<View> predicate)
+            throws InterruptedException {
+        if (!view.isAttachedToWindow()) {
+            throw new RuntimeException("Don't post Runnable on the view because it's detached from"
+                    + " window: " + view);
+        }
+        boolean[] result = new boolean[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        view.post(() -> {
+            result[0] = predicate.test(view);
+            latch.countDown();
+        });
+        assertWithMessage("Timeout")
+                .that(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isTrue();
+        return result[0];
+    }
+
+    /**
+     * Hides the focus in the view tree rooted at {@code root} by focusing on the given
+     * FocusParkingView {@code fpv} on the UI thread, and asserts it's done successfully.
+     */
+    public static void hideFocusAndAssertFocusHidden(@NonNull View root,
+            @NonNull FocusParkingView fpv) throws InterruptedException {
+        accept(root, v -> ViewUtils.hideFocus(v));
+        assertThat(fpv.isFocused()).isTrue();
+    }
+
+    /**
+     * Calls {@link ViewUtils#requestFocus} with {@code view} on the UI thread, and asserts that the
+     * method result and {@code view}'s focused state match {@code expectedFocused}.
+     * <p>
+     * The given {@code view} must be attached to window. Otherwise call
+     * {@link #requestFocusAndAssertFocused(View, boolean, View)} instead.
+     */
+    public static void requestFocusAndAssertFocused(@Nullable View view, boolean expectedFocused)
+            throws InterruptedException {
+        requestFocusAndAssertFocused(view, expectedFocused, view);
+    }
+
+    /**
+     * This method is a variant of {@link #requestFocusAndAssertFocused(View, boolean)}. It's
+     * used when {@code viewToFocus} is detached from window (e.g., it's removed). In this case,
+     * the Runnable is posted on {@code viewToPostRunnable}, which must be attached to window.
+     */
+    public static void requestFocusAndAssertFocused(@Nullable View viewToFocus,
+            boolean expectedFocused, @Nullable View viewToPostRunnable)
+            throws InterruptedException {
+        boolean result;
+        if (viewToFocus == null) {
+            result = ViewUtils.requestFocus(viewToFocus);
+            assertThat(result).isEqualTo(expectedFocused);
+            return;
+        }
+        result = test(viewToPostRunnable, v -> ViewUtils.requestFocus(viewToFocus));
+        assertThat(result).isEqualTo(expectedFocused);
+        assertThat(viewToFocus.isFocused()).isEqualTo(expectedFocused);
+    }
+
+    /**
+     * Calls {@link View#requestFocus} with {@code viewToRequestFocus} on the UI thread, and asserts
+     * that {@code viewToGetFocus} ends up being focused.
+     */
+    public static void requestFocusAndAssertFocused(@NonNull View viewToRequestFocus,
+            @NonNull View viewToGetFocus) throws InterruptedException {
+        accept(viewToRequestFocus, v -> v.requestFocus());
+        assertThat(viewToGetFocus.isFocused()).isTrue();
+    }
+
+    /**
+     * Adjusts focus in the view tree rooted at {@code root} on the UI thread, and asserts the
+     * returned value is {@code expectedSuccess}.
+     */
+    public static void adjustFocusAndAssertFocusAdjusted(@Nullable View root, int focusLevel,
+            boolean expectedSuccess) throws InterruptedException {
+        boolean result = test(root, v -> ViewUtils.adjustFocus(root, focusLevel));
+        assertThat(result).isEqualTo(expectedSuccess);
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
index d29772c..3f579fd 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/java/com/android/car/ui/utils/ViewUtilsTest.java
@@ -30,23 +30,22 @@
 import static com.android.car.ui.utils.ViewUtils.IMPLICIT_DEFAULT_FOCUS;
 import static com.android.car.ui.utils.ViewUtils.NO_FOCUS;
 import static com.android.car.ui.utils.ViewUtils.REGULAR_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.RESTORE_FOCUS_RETRY_DELAY_MS;
 import static com.android.car.ui.utils.ViewUtils.SCROLLABLE_CONTAINER_FOCUS;
+import static com.android.car.ui.utils.ViewUtils.SELECTED_FOCUS;
 import static com.android.car.ui.utils.ViewUtils.setRotaryScrollEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.view.View;
-import android.view.ViewTreeObserver;
 
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.car.rotary.test.R;
 import com.android.car.ui.FocusArea;
 import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.R;
 import com.android.car.ui.TestAdapter;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -65,13 +64,14 @@
     private FocusArea mFocusArea3;
     private FocusArea mFocusArea4;
     private FocusArea mFocusArea5;
-    private FocusParkingView mFpv;
     private View mView2;
     private View mFocusedByDefault3;
     private View mView4;
     private View mDefaultFocus4;
-    private RecyclerView mList5;
+    private CarUiRecyclerView mCarUiRecyclerView5;
+    private CarUiRecyclerView mCarUiRecyclerView6;
     private View mRoot;
+    private FocusParkingView mFpv;
 
     @Before
     public void setUp() {
@@ -81,18 +81,24 @@
         mFocusArea3 = mActivity.findViewById(R.id.focus_area3);
         mFocusArea4 = mActivity.findViewById(R.id.focus_area4);
         mFocusArea5 = mActivity.findViewById(R.id.focus_area5);
-        mFpv = mActivity.findViewById(R.id.fpv);
         mView2 = mActivity.findViewById(R.id.view2);
         mFocusedByDefault3 = mActivity.findViewById(R.id.focused_by_default3);
         mView4 = mActivity.findViewById(R.id.view4);
         mDefaultFocus4 = mActivity.findViewById(R.id.default_focus4);
-        mList5 = mActivity.findViewById(R.id.list5);
+        mCarUiRecyclerView5 = mActivity.findViewById(R.id.list5);
+        mCarUiRecyclerView6 = mActivity.findViewById(R.id.list6);
         mRoot = mFocusArea1.getRootView();
 
+        // Since ViewUtilsTestActivity uses Theme.CarUi.NoToolbar, a FocusParkingView has been added
+        // to the view tree automatically.
+        mFpv = ViewUtils.findFocusParkingView(mRoot);
+
         mRoot.post(() -> {
-            mList5.setLayoutManager(new LinearLayoutManager(mActivity));
-            mList5.setAdapter(new TestAdapter(/* numItems= */ 2));
-            setRotaryScrollEnabled(mList5, /* isVertical= */ true);
+            // Don't set the LayoutManager of mCarUiRecyclerView5 because it already has a default
+            // one, which contains important Runnables in its onLayoutCompleted(). Resetting it
+            // LayoutManager will remove the Runnables.
+            mCarUiRecyclerView5.setAdapter(new TestAdapter(/* numItems= */ 2));
+            setRotaryScrollEnabled(mCarUiRecyclerView5.getView(), /* isVertical= */ true);
         });
         // If we don't wait for the recyclerview items to show up, some of the tests flake
         onView(isRoot()).perform(waitForView(withText("Item 0"), 500));
@@ -100,424 +106,422 @@
 
     @Test
     public void testRootVisible() {
-        mRoot.post(() -> assertThat(mRoot.getVisibility()).isEqualTo(VISIBLE));
+        assertThat(mRoot.getVisibility()).isEqualTo(VISIBLE);
     }
 
     @Test
     public void testGetAncestorFocusArea() {
-        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mView2)).isEqualTo(mFocusArea2));
+        assertThat(ViewUtils.getAncestorFocusArea(mView2)).isEqualTo(mFocusArea2);
     }
 
     @Test
     public void testGetAncestorFocusArea_doesNotReturnItself() {
-        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFocusArea2)).isNull());
+        assertThat(ViewUtils.getAncestorFocusArea(mFocusArea2)).isNull();
     }
 
     @Test
     public void testGetAncestorFocusArea_outsideFocusArea() {
-        mRoot.post(() -> assertThat(ViewUtils.getAncestorFocusArea(mFpv)).isNull());
+        assertThat(ViewUtils.getAncestorFocusArea(mFpv)).isNull();
     }
 
     @Test
     public void testGetAncestorScrollableContainer() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                        assertThat(ViewUtils.getAncestorScrollableContainer(firstItem))
-                                .isEqualTo(mList5);
-                    }
-                }));
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        View container = ViewUtils.getAncestorScrollableContainer(firstItem);
+        // Since there is no API to get the inner RecyclerView, verify its focus level instead.
+        assertThat(ViewUtils.getFocusLevel(container)).isEqualTo(SCROLLABLE_CONTAINER_FOCUS);
     }
 
     @Test
     public void testGetAncestorScrollableContainer_returnNull() {
-        mRoot.post(() -> assertThat(ViewUtils.getAncestorScrollableContainer(mView2)).isNull());
+        assertThat(ViewUtils.getAncestorScrollableContainer(mView2)).isNull();
     }
 
     @Test
     public void testFindFocusedByDefaultView() {
-        mRoot.post(() -> {
-            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
-            assertThat(focusedByDefault).isEqualTo(mFocusedByDefault3);
-        });
+        View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+        assertThat(focusedByDefault).isEqualTo(mFocusedByDefault3);
     }
 
     @Test
-    public void testFindFocusedByDefaultView_skipNotFocusable() {
-        mRoot.post(() -> {
-            mFocusedByDefault3.setFocusable(false);
-            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
-            assertThat(focusedByDefault).isNull();
-        });
+    public void testFindFocusedByDefaultView_skipNotFocusable() throws InterruptedException {
+        TestUtils.accept(mFocusedByDefault3, v -> v.setFocusable(false));
+        View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+        assertThat(focusedByDefault).isNull();
     }
 
     @Test
-    public void testFindFocusedByDefaultView_skipInvisibleView() {
-        mRoot.post(() -> {
-            mFocusArea3.setVisibility(INVISIBLE);
-            assertThat(mFocusArea3.getVisibility()).isEqualTo(INVISIBLE);
-            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
-            assertThat(focusedByDefault).isNull();
-        });
+    public void testFindFocusedByDefaultView_skipInvisibleView() throws InterruptedException {
+        TestUtils.accept(mFocusArea3, v -> v.setVisibility(INVISIBLE));
+        assertThat(mFocusArea3.getVisibility()).isEqualTo(INVISIBLE);
+        View focusedByDefault = ViewUtils.findFocusedByDefaultView(mRoot);
+        assertThat(focusedByDefault).isNull();
     }
 
     @Test
-    public void testFindFocusedByDefaultView_skipInvisibleAncestor() {
-        mRoot.post(() -> {
-            mRoot.setVisibility(INVISIBLE);
-            View focusedByDefault = ViewUtils.findFocusedByDefaultView(mFocusArea3);
-            assertThat(focusedByDefault).isNull();
-        });
+    public void testFindFocusedByDefaultView_skipInvisibleAncestor() throws InterruptedException {
+        TestUtils.accept(mRoot, v -> v.setVisibility(INVISIBLE));
+        View focusedByDefault = ViewUtils.findFocusedByDefaultView(mFocusArea3);
+        assertThat(focusedByDefault).isNull();
     }
 
     @Test
     public void testFindImplicitDefaultFocusView_inRoot() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                        View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mRoot);
-                        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
-                    }
-                }));
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mRoot);
+        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
     }
 
     @Test
     public void testFindImplicitDefaultFocusView_inFocusArea() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                        View implicitDefaultFocus =
-                                ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
-                        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
-                    }
-                }));
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        View implicitDefaultFocus =
+                ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+        assertThat(implicitDefaultFocus).isEqualTo(firstItem);
     }
 
     @Test
-    public void testFindImplicitDefaultFocusView_skipInvisibleAncestor() {
-        mRoot.post(() -> {
-            mRoot.setVisibility(INVISIBLE);
-            View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
-            assertThat(implicitDefaultFocus).isNull();
-        });
+    public void testFindImplicitDefaultFocusView_skipInvisibleAncestor()
+            throws InterruptedException {
+        TestUtils.accept(mRoot, v -> v.setVisibility(INVISIBLE));
+        View implicitDefaultFocus = ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+        assertThat(implicitDefaultFocus).isNull();
     }
 
     @Test
-    public void testFindImplicitDefaultFocusView_selectedItem_inFocusArea() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View selectedItem = mList5.getLayoutManager().findViewByPosition(1);
-                        selectedItem.setSelected(true);
-                        View implicitDefaultFocus =
-                                ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
-                        assertThat(implicitDefaultFocus).isEqualTo(selectedItem);
-                    }
-                }));
+    public void testFindImplicitDefaultFocusView_selectedItem_inFocusArea()
+            throws InterruptedException {
+        View selectedItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(1);
+        TestUtils.accept(selectedItem, v -> v.setSelected(true));
+        View implicitDefaultFocus =
+                ViewUtils.findImplicitDefaultFocusView(mFocusArea5);
+        assertThat(implicitDefaultFocus).isEqualTo(selectedItem);
     }
 
     @Test
-    public void testFindFirstFocusableDescendant() {
-        mRoot.post(() -> {
-            mFocusArea2.setFocusable(true);
-            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
-            assertThat(firstFocusable).isEqualTo(mFocusArea2);
-        });
+    public void testFindFirstFocusableDescendant() throws InterruptedException {
+        TestUtils.accept(mFocusArea2, v -> v.setFocusable(true));
+        View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+        assertThat(firstFocusable).isEqualTo(mFocusArea2);
     }
 
     @Test
-    public void testFindFirstFocusableDescendant_skipItself() {
-        mRoot.post(() -> {
-            mFocusArea2.setFocusable(true);
-            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
-            assertThat(firstFocusable).isEqualTo(mView2);
-        });
+    public void testFindFirstFocusableDescendant_skipItself() throws InterruptedException {
+        TestUtils.accept(mFocusArea2, v -> v.setFocusable(true));
+        View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+        assertThat(firstFocusable).isEqualTo(mView2);
     }
 
     @Test
-    public void testFindFirstFocusableDescendant_skipInvisibleAndGoneView() {
-        mRoot.post(() -> {
+    public void testFindFirstFocusableDescendant_skipInvisibleAndGoneView()
+            throws InterruptedException {
+        TestUtils.accept(mRoot, v -> {
             mFocusArea2.setVisibility(INVISIBLE);
             mFocusArea3.setVisibility(GONE);
-            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
-            assertThat(firstFocusable).isEqualTo(mView4);
         });
+        View firstFocusable = ViewUtils.findFirstFocusableDescendant(mRoot);
+        assertThat(firstFocusable).isEqualTo(mView4);
     }
 
     @Test
-    public void testFindFirstFocusableDescendant_skipInvisibleAncestor() {
-        mRoot.post(() -> {
-            mRoot.setVisibility(INVISIBLE);
-            View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
-            assertThat(firstFocusable).isNull();
-        });
+    public void testFindFirstFocusableDescendant_skipInvisibleAncestor()
+            throws InterruptedException {
+        TestUtils.accept(mRoot, v -> v.setVisibility(View.INVISIBLE));
+        View firstFocusable = ViewUtils.findFirstFocusableDescendant(mFocusArea2);
+        assertThat(firstFocusable).isNull();
     }
 
     @Test
     public void testIsImplicitDefaultFocusView_firstItem() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isTrue();
-                    }
-                }));
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isTrue();
     }
 
     @Test
     public void testIsImplicitDefaultFocusView_secondItem() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View secondItem = mList5.getLayoutManager().findViewByPosition(1);
-                        assertThat(ViewUtils.isImplicitDefaultFocusView(secondItem)).isFalse();
-                    }
-                }));
+        View secondItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(1);
+        assertThat(ViewUtils.isImplicitDefaultFocusView(secondItem)).isFalse();
     }
 
     @Test
     public void testIsImplicitDefaultFocusView_normalView() {
-        mRoot.post(() -> assertThat(ViewUtils.isImplicitDefaultFocusView(mView2)).isFalse());
+        assertThat(ViewUtils.isImplicitDefaultFocusView(mView2)).isFalse();
     }
 
     @Test
-    public void testIsImplicitDefaultFocusView_skipInvisibleAncestor() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        mFocusArea5.setVisibility(INVISIBLE);
-                        View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isFalse();
-                    }
-                }));
+    public void testIsImplicitDefaultFocusView_skipInvisibleAncestor() throws InterruptedException {
+        TestUtils.accept(mFocusArea5, v -> v.setVisibility(View.INVISIBLE));
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        assertThat(ViewUtils.isImplicitDefaultFocusView(firstItem)).isFalse();
     }
 
     @Test
-    public void testIsImplicitDefaultFocusView_selectedItem() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        View selectedItem = mList5.getLayoutManager().findViewByPosition(1);
-                        selectedItem.setSelected(true);
-                        assertThat(ViewUtils.isImplicitDefaultFocusView(selectedItem)).isTrue();
-                    }
-                }));
+    public void testIsImplicitDefaultFocusView_selectedItem() throws InterruptedException {
+        View selectedItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(1);
+        TestUtils.accept(selectedItem, v -> v.setSelected(true));
+        assertThat(ViewUtils.isImplicitDefaultFocusView(selectedItem)).isTrue();
     }
 
     @Test
-    public void testRequestFocus() {
-        mRoot.post(() -> assertRequestFocus(mView2, true));
+    public void testRequestFocus() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
     }
 
     @Test
-    public void testRequestFocus_nullView() {
-        mRoot.post(() -> assertRequestFocus(null, false));
+    public void testRequestFocus_nullView() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(null, false);
     }
 
     @Test
-    public void testRequestFocus_alreadyFocused() {
+    public void testRequestFocus_alreadyFocused() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        // mView2 is already focused before requesting focus.
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+    }
+
+    @Test
+    public void testRequestFocus_notFocusable() throws InterruptedException {
+        TestUtils.accept(mView2, v -> v.setFocusable(false));
+        TestUtils.requestFocusAndAssertFocused(mView2, false);
+    }
+
+    @Test
+    public void testRequestFocus_disabled() throws InterruptedException {
+        TestUtils.accept(mView2, v -> v.setEnabled(false));
+        TestUtils.requestFocusAndAssertFocused(mView2, false);
+    }
+
+    @Test
+    public void testRequestFocus_notVisible() throws InterruptedException {
+        TestUtils.accept(mView2, v -> v.setVisibility(View.INVISIBLE));
+        TestUtils.requestFocusAndAssertFocused(mView2, false);
+    }
+
+    @Test
+    public void testRequestFocus_skipInvisibleAncestor() throws InterruptedException {
+        TestUtils.accept(mFocusArea2, v -> v.setVisibility(View.INVISIBLE));
+        TestUtils.requestFocusAndAssertFocused(mView2, false);
+    }
+
+    @Test
+    public void testRequestFocus_zeroWidth() throws InterruptedException {
+        TestUtils.accept(mView2, v -> v.setRight(v.getLeft()));
+        assertThat(mView2.getWidth()).isEqualTo(0);
+        TestUtils.requestFocusAndAssertFocused(mView2, false);
+    }
+
+    @Test
+    public void testRequestFocus_detachedFromWindow() throws InterruptedException {
+        TestUtils.accept(mFocusArea2, v -> mFocusArea2.removeView(mView2));
+        // mView2 is detached from window, so post the Runnable on another view mFocusArea2.
+        TestUtils.requestFocusAndAssertFocused(mView2, false,
+                /* viewToPostRunnable= */ mFocusArea2);
+    }
+
+    @Test
+    public void testRequestFocus_FocusParkingView() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        TestUtils.requestFocusAndAssertFocused(mFpv, false);
+    }
+
+    @Test
+    public void testAdjustFocus_rotaryContainer() throws InterruptedException {
+        mRoot.post(() -> setRotaryScrollEnabled(mCarUiRecyclerView5.getView(), false));
+        // This test verifies that the rotary container can't be focused because it's not focusable.
+        // Instead, its focusable descendant should get focused.
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mCarUiRecyclerView5.getView(), NO_FOCUS, true);
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        assertThat(firstItem.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testAdjustFocus_scrollableContainer() throws InterruptedException {
+        // This test verifies that the scrollable container can't be focused. Thought it's
+        // focusable, its focusable descendant should get focused.
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mCarUiRecyclerView5.getView(), NO_FOCUS, true);
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        assertThat(firstItem.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testAdjustFocus_inRoot() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        TestUtils.accept(mRoot, v -> ViewUtils.adjustFocus(mRoot, null));
+        assertThat(mFocusedByDefault3.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithDefaultFocus() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        TestUtils.accept(mRoot, v -> ViewUtils.adjustFocus(mFocusArea3, null));
+        assertThat(mFocusedByDefault3.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithoutDefaultFocus() throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView4, true);
+        TestUtils.accept(mRoot, v -> ViewUtils.adjustFocus(mFocusArea2, null));
+        assertThat(mView2.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testAdjustFocus_inFocusAreaWithoutFocusableDescendant()
+            throws InterruptedException {
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        boolean success = TestUtils.test(mRoot, v -> ViewUtils.adjustFocus(mFocusArea1, null));
+        assertThat(success).isFalse();
+        assertThat(mFocusArea1.hasFocus()).isFalse();
+    }
+
+    @Test
+    public void testAdjustFocus_differentFocusLevels() throws InterruptedException {
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea2, SCROLLABLE_CONTAINER_FOCUS, true);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea2, REGULAR_FOCUS, false);
+
+        mRoot.post(() -> mView2.setSelected(true));
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea2, REGULAR_FOCUS, true);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea2, SELECTED_FOCUS, false);
+
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea5, SELECTED_FOCUS, true);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea5, IMPLICIT_DEFAULT_FOCUS, false);
+
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea4, IMPLICIT_DEFAULT_FOCUS, true);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea4, DEFAULT_FOCUS, false);
+
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea3, DEFAULT_FOCUS, true);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea3, FOCUSED_BY_DEFAULT, false);
+
         mRoot.post(() -> {
-            assertRequestFocus(mView2, true);
-            // mView2 is already focused before requesting focus.
-            assertRequestFocus(mView2, true);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_notFocusable() {
-        mRoot.post(() -> {
-            mView2.setFocusable(false);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_disabled() {
-        mRoot.post(() -> {
-            mView2.setEnabled(false);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_notVisible() {
-        mRoot.post(() -> {
-            mView2.setVisibility(View.INVISIBLE);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_skipInvisibleAncestor() {
-        mRoot.post(() -> {
-            mFocusArea2.setVisibility(View.INVISIBLE);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_zeroWidth() {
-        mRoot.post(() -> {
-            mView2.setRight(mView2.getLeft());
-            assertThat(mView2.getWidth()).isEqualTo(0);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_detachedFromWindow() {
-        mRoot.post(() -> {
-            mFocusArea2.removeView(mView2);
-            assertRequestFocus(mView2, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_FocusParkingView() {
-        mRoot.post(() -> {
-            assertRequestFocus(mView2, true);
-            assertRequestFocus(mFpv, false);
-        });
-    }
-
-    @Test
-    public void testRequestFocus_rotaryContainer() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        assertRequestFocus(mList5, false);
-                    }
-                }));
-    }
-
-    @Test
-    public void testRequestFocus_scrollableContainer() {
-        mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                        assertRequestFocus(mList5, false);
-                    }
-                }));
-    }
-
-    @Test
-    public void testAdjustFocus_inRoot() {
-        mRoot.post(() -> {
-            assertRequestFocus(mView2, true);
-            ViewUtils.adjustFocus(mRoot, null);
-            assertThat(mFocusedByDefault3.isFocused()).isTrue();
-        });
-    }
-
-    @Test
-    public void testAdjustFocus_inFocusAreaWithDefaultFocus() {
-        mRoot.post(() -> {
-            assertRequestFocus(mView2, true);
-            ViewUtils.adjustFocus(mFocusArea3, null);
-            assertThat(mFocusedByDefault3.isFocused()).isTrue();
-        });
-    }
-
-    @Test
-    public void testAdjustFocus_inFocusAreaWithoutDefaultFocus() {
-        mRoot.post(() -> {
-            assertRequestFocus(mView4, true);
-            ViewUtils.adjustFocus(mFocusArea2, null);
-            assertThat(mView2.isFocused()).isTrue();
-        });
-    }
-
-    @Test
-    public void testAdjustFocus_inFocusAreaWithoutFocusableDescendant() {
-        mRoot.post(() -> {
-            assertRequestFocus(mView2, true);
-            boolean success = ViewUtils.adjustFocus(mFocusArea1, null);
-            assertThat(mFocusArea1.hasFocus()).isFalse();
-            assertThat(success).isFalse();
-        });
-    }
-
-    @Test
-    public void testAdjustFocus_differentFocusLevels() {
-        mRoot.post(() -> {
-            assertThat(ViewUtils.adjustFocus(mFocusArea2, SCROLLABLE_CONTAINER_FOCUS)).isTrue();
-            assertThat(ViewUtils.adjustFocus(mFocusArea2, REGULAR_FOCUS)).isFalse();
-
-            assertThat(ViewUtils.adjustFocus(mFocusArea5, REGULAR_FOCUS)).isTrue();
-            assertThat(ViewUtils.adjustFocus(mFocusArea5, IMPLICIT_DEFAULT_FOCUS)).isFalse();
-
-            assertThat(ViewUtils.adjustFocus(mFocusArea4, IMPLICIT_DEFAULT_FOCUS)).isTrue();
-            assertThat(ViewUtils.adjustFocus(mFocusArea4, DEFAULT_FOCUS)).isFalse();
-
-            assertThat(ViewUtils.adjustFocus(mFocusArea3, DEFAULT_FOCUS)).isTrue();
-            assertThat(ViewUtils.adjustFocus(mFocusArea3, FOCUSED_BY_DEFAULT)).isFalse();
-
-            View firstItem = mList5.getLayoutManager().findViewByPosition(0);
+            View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
             firstItem.setFocusable(false);
-            View secondItem = mList5.getLayoutManager().findViewByPosition(1);
+            View secondItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(1);
             secondItem.setFocusable(false);
-            assertThat(ViewUtils.adjustFocus(mFocusArea5, NO_FOCUS)).isTrue();
-            assertThat(ViewUtils.adjustFocus(mFocusArea5, SCROLLABLE_CONTAINER_FOCUS)).isFalse();
         });
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea5, NO_FOCUS, true);
+        ViewUtils.adjustFocus(mFocusArea5, NO_FOCUS);
+        TestUtils.adjustFocusAndAssertFocusAdjusted(mFocusArea5, SCROLLABLE_CONTAINER_FOCUS, false);
     }
 
     @Test
     public void testGetFocusLevel() {
-        mRoot.post(() -> {
-            assertThat(ViewUtils.getFocusLevel(null)).isEqualTo(NO_FOCUS);
-            assertThat(ViewUtils.getFocusLevel(mFpv)).isEqualTo(NO_FOCUS);
-            mFocusArea2.setVisibility(INVISIBLE);
-            assertThat(ViewUtils.getFocusLevel(mView2)).isEqualTo(NO_FOCUS);
+        assertThat(ViewUtils.getFocusLevel(null)).isEqualTo(NO_FOCUS);
+        assertThat(ViewUtils.getFocusLevel(mFpv)).isEqualTo(NO_FOCUS);
+        mFocusArea2.setVisibility(INVISIBLE);
+        assertThat(ViewUtils.getFocusLevel(mView2)).isEqualTo(NO_FOCUS);
 
-            assertThat(ViewUtils.getFocusLevel(mList5)).isEqualTo(SCROLLABLE_CONTAINER_FOCUS);
+        // SCROLLABLE_CONTAINER_FOCUS is tested in testGetAncestorScrollableContainer().
 
-            assertThat(ViewUtils.getFocusLevel(mView4)).isEqualTo(REGULAR_FOCUS);
+        assertThat(ViewUtils.getFocusLevel(mView4)).isEqualTo(REGULAR_FOCUS);
 
-            mRoot.post(() -> mList5.getViewTreeObserver().addOnGlobalLayoutListener(
-                    new ViewTreeObserver.OnGlobalLayoutListener() {
-                        @Override
-                        public void onGlobalLayout() {
-                            mList5.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                            View firstItem = mList5.getLayoutManager().findViewByPosition(0);
-                            assertThat(ViewUtils.getFocusLevel(firstItem))
-                                    .isEqualTo(IMPLICIT_DEFAULT_FOCUS);
-                        }
-                    }));
+        mView4.setSelected(true);
+        assertThat(ViewUtils.getFocusLevel(mView4)).isEqualTo(SELECTED_FOCUS);
 
-            assertThat(ViewUtils.getFocusLevel(mDefaultFocus4)).isEqualTo(DEFAULT_FOCUS);
+        View firstItem = mCarUiRecyclerView5.getLayoutManager().findViewByPosition(0);
+        assertThat(ViewUtils.getFocusLevel(firstItem)).isEqualTo(IMPLICIT_DEFAULT_FOCUS);
 
-            assertThat(ViewUtils.getFocusLevel(mFocusedByDefault3)).isEqualTo(FOCUSED_BY_DEFAULT);
-        });
+        assertThat(ViewUtils.getFocusLevel(mDefaultFocus4)).isEqualTo(DEFAULT_FOCUS);
+
+        assertThat(ViewUtils.getFocusLevel(mFocusedByDefault3)).isEqualTo(FOCUSED_BY_DEFAULT);
     }
 
-    private static void assertRequestFocus(@Nullable View view, boolean focused) {
-        boolean result = ViewUtils.requestFocus(view);
-        assertThat(result).isEqualTo(focused);
-        if (view != null) {
-            assertThat(view.isFocused()).isEqualTo(focused);
+    @Test
+    public void testInitFocus_inLazyLayoutView1() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView5;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isTrue();
+        TestUtils.requestFocusAndAssertFocused(mView2, true);
+        mRoot.post(() -> ViewUtils.initFocus(lazyLayoutView));
+        waitForFocusRestored();
+        // The focus shouldn't change because there was a visible focus.
+        assertThat(mView2.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testInitFocus_inLazyLayoutView2() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView5;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isTrue();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        mRoot.post(() -> ViewUtils.initFocus(lazyLayoutView));
+        waitForFocusRestored();
+        // The focus should move into the lazyLayoutView because there was no visible focus.
+        assertThat(mCarUiRecyclerView5.getView().hasFocus()).isTrue();
+    }
+
+    @Test
+    public void testInitFocus_inLazyLayoutView3() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView6;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isFalse();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        mRoot.post(() -> {
+            // mCarUiRecyclerView6 hasn't completed layout when initializing focus.
+            ViewUtils.initFocus(lazyLayoutView);
+            mCarUiRecyclerView6.setAdapter(new TestAdapter(/* numItems= */ 2));
+        });
+        waitForFocusRestored();
+        // mCarUiRecyclerView6 has completed layout, so the focus should be restored successfully.
+        assertThat(lazyLayoutView.isLayoutCompleted()).isTrue();
+        assertThat(mCarUiRecyclerView6.getView().hasFocus()).isTrue();
+    }
+
+    @Test
+    public void testInitFocus_inLazyLayoutView4() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView6;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isFalse();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        mRoot.post(() -> {
+            // mCarUiRecyclerView6 will never complete layout because its adapter has never been
+            // set.
+            ViewUtils.initFocus(lazyLayoutView);
+        });
+        waitForFocusRestored();
+        // The focus should move to the best view in the view tree as fallback.
+        assertThat(mFocusedByDefault3.isFocused()).isTrue();
+    }
+
+    @Test
+    public void testInitFocus_inLazyLayoutView5() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView5;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isTrue();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        mRoot.post(() -> {
+            // mCarUiRecyclerView5 has completed layout, but it is invisible now. It will become
+            // visible after ViewUtils.initFocus() is called..
+            mFocusArea5.setVisibility(INVISIBLE);
+            ViewUtils.initFocus(lazyLayoutView);
+            mFocusArea5.setVisibility(VISIBLE);
+        });
+        waitForFocusRestored();
+        // The focus should be restored successfully.
+        assertThat(mCarUiRecyclerView5.getView().hasFocus()).isTrue();
+    }
+
+    @Test
+    public void testInitFocus_inLazyLayoutView6() throws InterruptedException {
+        ViewUtils.LazyLayoutView lazyLayoutView =
+                (ViewUtils.LazyLayoutView) mCarUiRecyclerView5;
+        assertThat(lazyLayoutView.isLayoutCompleted()).isTrue();
+        TestUtils.hideFocusAndAssertFocusHidden(mRoot, mFpv);
+        mRoot.post(() -> {
+            mFocusArea5.setVisibility(INVISIBLE);
+            ViewUtils.initFocus(lazyLayoutView);
+        });
+        // mCarUiRecyclerView5 has completed layout, but it's invisible forever, so it should move
+        // to the best view in the view tree as fallback.
+        waitForFocusRestored();
+        assertThat(mFocusedByDefault3.isFocused()).isTrue();
+    }
+
+    private static void waitForFocusRestored() {
+        try {
+            // Wait longer than RESTORE_FOCUS_RETRY_DELAY_MS to make sure the delayedTask in
+            // ViewUtils.initFocusDelayed() has completed.
+            Thread.sleep(RESTORE_FOCUS_RETRY_DELAY_MS + 1000);
+        } catch (InterruptedException e) {
+            throw new AssertionError("Unexpected InterruptedException", e);
         }
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/direct_manipulation_helper_test_activity.xml
similarity index 73%
rename from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
rename to car-ui-lib/car-rotary-lib/src/androidTest/res/layout/direct_manipulation_helper_test_activity.xml
index 6a35b43..444bfd4 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/direct_manipulation_helper_test_activity.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,11 +14,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<FrameLayout
+<LinearLayout
     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>
+    android:layout_height="match_parent">
+    <View
+        android:id="@+id/view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
index 02d7326..9c3673b 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
@@ -18,10 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <com.android.car.ui.FocusParkingView
-        android:id="@+id/fpv"
-        android:layout_width="10dp"
-        android:layout_height="10dp"/>
     <LinearLayout
         android:id="@+id/parent1"
         android:layout_width="wrap_content"
diff --git a/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/view_utils_test_activity.xml b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/view_utils_test_activity.xml
index c6470ba..fc2d495 100644
--- a/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/view_utils_test_activity.xml
+++ b/car-ui-lib/car-rotary-lib/src/androidTest/res/layout/view_utils_test_activity.xml
@@ -19,10 +19,6 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <com.android.car.ui.FocusParkingView
-        android:id="@+id/fpv"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
     <com.android.car.ui.FocusArea
         android:id="@+id/focus_area1"
         android:layout_width="wrap_content"
@@ -84,9 +80,19 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical">
-        <androidx.recyclerview.widget.RecyclerView
+        <com.android.car.ui.recyclerview.CarUiRecyclerView
             android:id="@+id/list5"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"/>
     </com.android.car.ui.FocusArea>
+    <com.android.car.ui.FocusArea
+        android:id="@+id/focus_area6"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <com.android.car.ui.recyclerview.CarUiRecyclerView
+            android:id="@+id/list6"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+    </com.android.car.ui.FocusArea>
 </LinearLayout>
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/ConstraintFocusArea.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/ConstraintFocusArea.java
new file mode 100644
index 0000000..9a73519
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/ConstraintFocusArea.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import static com.android.car.ui.utils.RotaryConstants.I_FOCUS_AREA_CLASS_NAME;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.FocusFinder;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+/**
+ * A {@link ConstraintLayout} used as a navigation block for the rotary controller.
+ * <p>
+ * When creating a navigation block in the layout file, if you intend to use a ConstraintLayout as a
+ * container for that block, just use a ConstraintFocusArea instead; otherwise use other
+ * {@link IFocusArea} implementations, such as {@link FocusArea} which extends
+ * {@link android.widget.LinearLayout}.
+ * <p>
+ * DO NOT nest an IFocusArea inside another IFocusArea because it will result in undefined
+ * navigation behavior.
+ */
+public class ConstraintFocusArea extends ConstraintLayout implements IFocusArea {
+
+    @NonNull
+    private final FocusAreaHelper mFocusAreaHelper;
+
+    public ConstraintFocusArea(Context context) {
+        this(context, null);
+    }
+
+    public ConstraintFocusArea(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ConstraintFocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ConstraintFocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mFocusAreaHelper = new FocusAreaHelper(this, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFocusAreaHelper.onFinishInflate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mFocusAreaHelper.onLayout();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mFocusAreaHelper.onAttachedToWindow();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mFocusAreaHelper.onDetachedFromWindow();
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!mFocusAreaHelper.onWindowFocusChanged(hasWindowFocus())) {
+            super.onWindowFocusChanged(hasWindowFocus);
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (mFocusAreaHelper.isFocusAreaAction(action)) {
+            return mFocusAreaHelper.performAccessibilityAction(action, arguments);
+        }
+        return super.performAccessibilityAction(action, arguments);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mFocusAreaHelper.onDraw(canvas);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        mFocusAreaHelper.draw(canvas);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return I_FOCUS_AREA_CLASS_NAME;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        mFocusAreaHelper.onInitializeAccessibilityNodeInfo(info);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        if (isInTouchMode()) {
+            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+        }
+        return mFocusAreaHelper.onRequestFocusInDescendants();
+    }
+
+    @Override
+    public boolean restoreDefaultFocus() {
+        return mFocusAreaHelper.restoreDefaultFocus();
+    }
+
+    /**
+     * @inheritDoc
+     * <p>
+     * When wrap-around is allowed, the search is restricted to descendants of this
+     * {@link ConstraintFocusArea}.
+     */
+    @Override
+    public View focusSearch(View focused, int direction) {
+        if (mFocusAreaHelper.isWrapAround()) {
+            return FocusFinder.getInstance().findNextFocus(/* root= */ this, focused, direction);
+        }
+        return super.focusSearch(focused, direction);
+    }
+
+    @VisibleForTesting
+    @Override
+    @NonNull
+    public FocusAreaHelper getHelper() {
+        return mFocusAreaHelper;
+    }
+
+    @Override
+    public View getDefaultFocusView() {
+        return mFocusAreaHelper.getDefaultFocusView();
+    }
+
+    @Override
+    public void setDefaultFocus(@NonNull View defaultFocus) {
+        mFocusAreaHelper.setDefaultFocus(defaultFocus);
+    }
+
+    @Override
+    public void setHighlightPadding(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setHighlightPadding(left, top, right, bottom);
+    }
+
+    @Override
+    public void setBoundsOffset(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setBoundsOffset(left, top, right, bottom);
+    }
+
+    @Override
+    public void setWrapAround(boolean wrapAround) {
+        mFocusAreaHelper.setWrapAround(wrapAround);
+    }
+
+    @Override
+    public void setNudgeShortcut(int direction, @Nullable View view) {
+        mFocusAreaHelper.setNudgeShortcut(direction, view);
+    }
+
+    @Override
+    public void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target) {
+        mFocusAreaHelper.setNudgeTargetFocusArea(direction, target);
+    }
+
+    @Override
+    public void setDefaultFocusOverridesHistory(boolean override) {
+        mFocusAreaHelper.setDefaultFocusOverridesHistory(override);
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusArea.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusArea.java
index bc9ff3a..3b49dbf 100644
--- a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusArea.java
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusArea.java
@@ -16,33 +16,15 @@
 
 package com.android.car.ui;
 
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
-
-import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_SHORTCUT;
-import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA;
-import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
-import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
-import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
-import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
-import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
+import static com.android.car.ui.utils.RotaryConstants.I_FOCUS_AREA_CLASS_NAME;
 
 import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.FocusFinder;
-import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
@@ -50,661 +32,99 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.car.ui.utils.ViewUtils;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A {@link LinearLayout} used as a navigation block for the rotary controller.
  * <p>
- * The {@link com.android.car.rotary.RotaryService} looks for instances of {@link FocusArea} in the
- * view hierarchy when handling rotate and nudge actions. When receiving a rotation event ({@link
- * android.car.input.RotaryEvent}), RotaryService will move the focus to another {@link View} that
- * can take focus within the same FocusArea. When receiving a nudge event ({@link
- * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_UP}, {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_DOWN}, {@link
- * KeyEvent#KEYCODE_SYSTEM_NAVIGATION_LEFT}, or {@link KeyEvent#KEYCODE_SYSTEM_NAVIGATION_RIGHT}),
- * RotaryService will move the focus to another view that can take focus in another (typically
- * adjacent) FocusArea.
- * <p>
- * If enabled, FocusArea can draw highlights when one of its descendants has focus and it's not in
- * touch mode.
- * <p>
  * When creating a navigation block in the layout file, if you intend to use a LinearLayout as a
- * container for that block, just use a FocusArea instead; otherwise wrap the block in a FocusArea.
+ * container for that block, just use a FocusArea instead; otherwise use other {@link IFocusArea}
+ * implementations, such as {@link FrameFocusArea} which extends {@link android.widget.FrameLayout}.
  * <p>
- * DO NOT nest a FocusArea inside another FocusArea because it will result in undefined navigation
- * behavior.
+ * DO NOT nest an IFocusArea inside another IFocusArea because it will result in undefined
+ * navigation behavior.
  */
-public class FocusArea extends LinearLayout {
+public class FocusArea extends LinearLayout implements IFocusArea {
 
-    private static final String TAG = "FocusArea";
-
-    private static final int INVALID_DIMEN = -1;
-
-    private static final int INVALID_DIRECTION = -1;
-
-    private static final List<Integer> NUDGE_DIRECTIONS =
-            Collections.unmodifiableList(Arrays.asList(
-                    FOCUS_LEFT, FOCUS_RIGHT, FOCUS_UP, FOCUS_DOWN));
-
-    /** Whether the FocusArea's descendant has focus (the FocusArea itself is not focusable). */
-    private boolean mHasFocus;
-
-    /**
-     * Whether to draw {@link #mForegroundHighlight} when one of the FocusArea's descendants has
-     * focus and it's not in touch mode.
-     */
-    private boolean mEnableForegroundHighlight;
-
-    /**
-     * Whether to draw {@link #mBackgroundHighlight} when one of the FocusArea's descendants has
-     * focus and it's not in touch mode.
-     */
-    private boolean mEnableBackgroundHighlight;
-
-    /**
-     * Highlight (typically outline of the FocusArea) drawn on top of the FocusArea and its
-     * descendants.
-     */
-    private Drawable mForegroundHighlight;
-
-    /**
-     * Highlight (typically a solid or gradient shape) drawn on top of the FocusArea but behind its
-     * descendants.
-     */
-    private Drawable mBackgroundHighlight;
-
-    /** The padding (in pixels) of the FocusArea highlight. */
-    private int mPaddingLeft;
-    private int mPaddingRight;
-    private int mPaddingTop;
-    private int mPaddingBottom;
-
-    /** The offset (in pixels) of the FocusArea's bounds. */
-    private int mLeftOffset;
-    private int mRightOffset;
-    private int mTopOffset;
-    private int mBottomOffset;
-
-    /** Whether the layout direction is {@link View#LAYOUT_DIRECTION_RTL}. */
-    private boolean mRtl;
-
-    /** The ID of the view specified in {@code app:defaultFocus}. */
-    private int mDefaultFocusId;
-    /** The view specified in {@code app:defaultFocus}. */
-    @Nullable
-    private View mDefaultFocusView;
-
-    /**
-     * Whether to focus on the default focus view when nudging to the FocusArea, even if there was
-     * another view in the FocusArea focused before.
-     */
-    private boolean mDefaultFocusOverridesHistory;
-
-    /**
-     * Map from direction to nudge shortcut IDs specified in {@code app:nudgeLeftShortcut},
-     * {@code app:nudgRightShortcut}, {@code app:nudgeUpShortcut}, and {@code app
-     * :nudgeDownShortcut}.
-     */
-    private final SparseIntArray mSpecifiedNudgeShortcutIdMap = new SparseIntArray();
-
-    /** Map from direction to specified nudge shortcut views. */
-    private SparseArray<View> mSpecifiedNudgeShortcutMap;
-
-    /**
-     * Map from direction to nudge target FocusArea IDs specified in {@code app:nudgeLeft},
-     * {@code app:nudgRight}, {@code app:nudgeUp}, or {@code app:nudgeDown}.
-     */
-    private final SparseIntArray mSpecifiedNudgeIdMap = new SparseIntArray();
-
-    /** Map from direction to specified nudge target FocusAreas. */
-    private SparseArray<FocusArea> mSpecifiedNudgeFocusAreaMap;
-
-    /** Whether wrap-around is enabled. */
-    private boolean mWrapAround;
-
-    /**
-     * Cache of focus history and nudge history of the rotary controller.
-     * <p>
-     * For focus history, the previously focused view and a timestamp will be saved when the
-     * focused view has changed.
-     * <p>
-     * For nudge history, the target FocusArea, direction, and a timestamp will be saved when the
-     * focus has moved from another FocusArea to this FocusArea. There are 2 cases:
-     * <ul>
-     *     <li>The focus is moved to another FocusArea because this FocusArea has called {@link
-     *         #nudgeToAnotherFocusArea}. In this case, the target FocusArea and direction are
-     *         trivial to this FocusArea.
-     *     <li>The focus is moved to this FocusArea because RotaryService has performed {@link
-     *         AccessibilityNodeInfo#ACTION_FOCUS} on this FocusArea. In this case, this FocusArea
-     *         can get the source FocusArea through the {@link
-     *         android.view.ViewTreeObserver.OnGlobalFocusChangeListener} registered, and can get
-     *         the direction when handling the action. Since the listener is triggered before
-     *         {@link #requestFocus} returns (which is called when handling the action), the
-     *         source FocusArea is revealed earlier than the direction, so the nudge history should
-     *         be saved when the direction is revealed.
-     * </ul>
-     */
-    private RotaryCache mRotaryCache;
-
-    /** Whether to clear focus area history when the user rotates the rotary controller. */
-    private boolean mClearFocusAreaHistoryWhenRotating;
-
-    /** The FocusArea that had focus before this FocusArea, if any. */
-    private FocusArea mPreviousFocusArea;
-
-    /** The focused view in this FocusArea, if any. */
-    private View mFocusedView;
-
-    private final OnGlobalFocusChangeListener mFocusChangeListener =
-            (oldFocus, newFocus) -> {
-                boolean hasFocus = hasFocus();
-                saveFocusHistory(hasFocus);
-                maybeUpdatePreviousFocusArea(hasFocus, oldFocus);
-                maybeClearFocusAreaHistory(hasFocus, oldFocus);
-                maybeUpdateFocusAreaHighlight(hasFocus);
-                mHasFocus = hasFocus;
-            };
+    @NonNull
+    private final FocusAreaHelper mFocusAreaHelper;
 
     public FocusArea(Context context) {
-        super(context);
-        init(context, null);
+        this(context, null);
     }
 
     public FocusArea(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs);
+        this(context, attrs, 0);
     }
 
     public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        init(context, attrs);
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init(context, attrs);
-    }
-
-    private void init(Context context, @Nullable AttributeSet attrs) {
-        Resources resources = getContext().getResources();
-        mEnableForegroundHighlight = resources.getBoolean(
-                R.bool.car_ui_enable_focus_area_foreground_highlight);
-        mEnableBackgroundHighlight = resources.getBoolean(
-                R.bool.car_ui_enable_focus_area_background_highlight);
-        mForegroundHighlight = resources.getDrawable(
-                R.drawable.car_ui_focus_area_foreground_highlight, getContext().getTheme());
-        mBackgroundHighlight = resources.getDrawable(
-                R.drawable.car_ui_focus_area_background_highlight, getContext().getTheme());
-
-        mClearFocusAreaHistoryWhenRotating = resources.getBoolean(
-                R.bool.car_ui_clear_focus_area_history_when_rotating);
-
-        @RotaryCache.CacheType
-        int focusHistoryCacheType = resources.getInteger(R.integer.car_ui_focus_history_cache_type);
-        int focusHistoryExpirationPeriodMs =
-                resources.getInteger(R.integer.car_ui_focus_history_expiration_period_ms);
-        @RotaryCache.CacheType
-        int focusAreaHistoryCacheType = resources.getInteger(
-                R.integer.car_ui_focus_area_history_cache_type);
-        int focusAreaHistoryExpirationPeriodMs =
-                resources.getInteger(R.integer.car_ui_focus_area_history_expiration_period_ms);
-        mRotaryCache = new RotaryCache(focusHistoryCacheType, focusHistoryExpirationPeriodMs,
-                focusAreaHistoryCacheType, focusAreaHistoryExpirationPeriodMs);
-
-        // Ensure that an AccessibilityNodeInfo is created for this view.
-        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-
-        // By default all ViewGroup subclasses do not call their draw() and onDraw() methods. We
-        // should enable it since we override these methods.
-        setWillNotDraw(false);
-
-        initAttrs(context, attrs);
-    }
-
-    private void saveFocusHistory(boolean hasFocus) {
-        // Save focus history and clear mFocusedView if focus is leaving this FocusArea.
-        if (!hasFocus) {
-            if (mHasFocus) {
-                mRotaryCache.saveFocusedView(mFocusedView, SystemClock.uptimeMillis());
-                mFocusedView = null;
-            }
-            return;
-        }
-
-        // Update mFocusedView if a view inside this FocusArea is focused.
-        View v = getFocusedChild();
-        while (v != null) {
-            if (v.isFocused()) {
-                break;
-            }
-            v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
-        }
-        mFocusedView = v;
-    }
-
-    /**
-     * Updates {@link #mPreviousFocusArea} when the focus has moved from another FocusArea to this
-     * FocusArea, and sets it to {@code null} in any other cases.
-     */
-    private void maybeUpdatePreviousFocusArea(boolean hasFocus, View oldFocus) {
-        if (mHasFocus || !hasFocus || oldFocus == null || oldFocus instanceof FocusParkingView) {
-            mPreviousFocusArea = null;
-            return;
-        }
-        mPreviousFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
-        if (mPreviousFocusArea == null) {
-            Log.w(TAG, "No parent FocusArea for " + oldFocus);
-        }
-    }
-
-    /**
-     * Clears FocusArea nudge history when the user rotates the controller to move focus within this
-     * FocusArea.
-     */
-    private void maybeClearFocusAreaHistory(boolean hasFocus, View oldFocus) {
-        if (!mClearFocusAreaHistoryWhenRotating) {
-            return;
-        }
-        if (!hasFocus || oldFocus == null) {
-            return;
-        }
-        FocusArea oldFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
-        if (oldFocusArea != this) {
-            return;
-        }
-        mRotaryCache.clearFocusAreaHistory();
-    }
-
-    /** Updates highlight of the FocusArea if this FocusArea has gained or lost focus. */
-    private void maybeUpdateFocusAreaHighlight(boolean hasFocus) {
-        if (!mEnableBackgroundHighlight && !mEnableForegroundHighlight) {
-            return;
-        }
-        if (mHasFocus != hasFocus) {
-            invalidate();
-        }
-    }
-
-    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
-        if (attrs == null) {
-            return;
-        }
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FocusArea);
-        try {
-            mDefaultFocusId = a.getResourceId(R.styleable.FocusArea_defaultFocus, View.NO_ID);
-
-            // Initialize the highlight padding. The padding, for example, left padding, is set in
-            // the following order:
-            // 1. if highlightPaddingStart (or highlightPaddingEnd in RTL layout) specified, use it
-            // 2. otherwise, if highlightPaddingHorizontal is specified, use it
-            // 3. otherwise use 0
-
-            int paddingStart = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_highlightPaddingStart, INVALID_DIMEN);
-            if (paddingStart == INVALID_DIMEN) {
-                paddingStart = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_highlightPaddingHorizontal, 0);
-            }
-
-            int paddingEnd = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_highlightPaddingEnd, INVALID_DIMEN);
-            if (paddingEnd == INVALID_DIMEN) {
-                paddingEnd = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_highlightPaddingHorizontal, 0);
-            }
-
-            mRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-            mPaddingLeft = mRtl ? paddingEnd : paddingStart;
-            mPaddingRight = mRtl ? paddingStart : paddingEnd;
-
-            mPaddingTop = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_highlightPaddingTop, INVALID_DIMEN);
-            if (mPaddingTop == INVALID_DIMEN) {
-                mPaddingTop = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_highlightPaddingVertical, 0);
-            }
-
-            mPaddingBottom = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_highlightPaddingBottom, INVALID_DIMEN);
-            if (mPaddingBottom == INVALID_DIMEN) {
-                mPaddingBottom = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_highlightPaddingVertical, 0);
-            }
-
-            // Initialize the offset of the FocusArea's bounds. The offset, for example, left
-            // offset, is set in the following order:
-            // 1. if startBoundOffset (or endBoundOffset in RTL layout) specified, use it
-            // 2. otherwise, if horizontalBoundOffset is specified, use it
-            // 3. otherwise use mPaddingLeft
-
-            int startOffset = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_startBoundOffset, INVALID_DIMEN);
-            if (startOffset == INVALID_DIMEN) {
-                startOffset = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_horizontalBoundOffset, paddingStart);
-            }
-
-            int endOffset = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_endBoundOffset, INVALID_DIMEN);
-            if (endOffset == INVALID_DIMEN) {
-                endOffset = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_horizontalBoundOffset, paddingEnd);
-            }
-
-            mLeftOffset = mRtl ? endOffset : startOffset;
-            mRightOffset = mRtl ? startOffset : endOffset;
-
-            mTopOffset = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_topBoundOffset, INVALID_DIMEN);
-            if (mTopOffset == INVALID_DIMEN) {
-                mTopOffset = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_verticalBoundOffset, mPaddingTop);
-            }
-
-            mBottomOffset = a.getDimensionPixelSize(
-                    R.styleable.FocusArea_bottomBoundOffset, INVALID_DIMEN);
-            if (mBottomOffset == INVALID_DIMEN) {
-                mBottomOffset = a.getDimensionPixelSize(
-                        R.styleable.FocusArea_verticalBoundOffset, mPaddingBottom);
-            }
-
-            // Handle new nudge shortcut attributes.
-            if (a.hasValue(R.styleable.FocusArea_nudgeLeftShortcut)) {
-                mSpecifiedNudgeShortcutIdMap.put(FOCUS_LEFT,
-                        a.getResourceId(R.styleable.FocusArea_nudgeLeftShortcut, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeRightShortcut)) {
-                mSpecifiedNudgeShortcutIdMap.put(FOCUS_RIGHT,
-                        a.getResourceId(R.styleable.FocusArea_nudgeRightShortcut, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeUpShortcut)) {
-                mSpecifiedNudgeShortcutIdMap.put(FOCUS_UP,
-                        a.getResourceId(R.styleable.FocusArea_nudgeUpShortcut, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeDownShortcut)) {
-                mSpecifiedNudgeShortcutIdMap.put(FOCUS_DOWN,
-                        a.getResourceId(R.styleable.FocusArea_nudgeDownShortcut, View.NO_ID));
-            }
-
-            // Handle legacy nudge shortcut attributes.
-            int nudgeShortcutId = a.getResourceId(R.styleable.FocusArea_nudgeShortcut, View.NO_ID);
-            int nudgeShortcutDirection = a.getInt(
-                    R.styleable.FocusArea_nudgeShortcutDirection, INVALID_DIRECTION);
-            if ((nudgeShortcutId == View.NO_ID) ^ (nudgeShortcutDirection == INVALID_DIRECTION)) {
-                throw new IllegalStateException("nudgeShortcut and nudgeShortcutDirection must "
-                        + "be specified together");
-            }
-            if (nudgeShortcutId != View.NO_ID) {
-                if (mSpecifiedNudgeShortcutIdMap.size() > 0) {
-                    throw new IllegalStateException(
-                            "Don't use nudgeShortcut/nudgeShortcutDirection and nudge*Shortcut in "
-                                    + "the same FocusArea. Use nudge*Shortcut only.");
-                }
-                mSpecifiedNudgeShortcutIdMap.put(nudgeShortcutDirection, nudgeShortcutId);
-            }
-
-            // Handle nudge targets.
-            if (a.hasValue(R.styleable.FocusArea_nudgeLeft)) {
-                mSpecifiedNudgeIdMap.put(FOCUS_LEFT,
-                        a.getResourceId(R.styleable.FocusArea_nudgeLeft, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeRight)) {
-                mSpecifiedNudgeIdMap.put(FOCUS_RIGHT,
-                        a.getResourceId(R.styleable.FocusArea_nudgeRight, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeUp)) {
-                mSpecifiedNudgeIdMap.put(FOCUS_UP,
-                        a.getResourceId(R.styleable.FocusArea_nudgeUp, View.NO_ID));
-            }
-            if (a.hasValue(R.styleable.FocusArea_nudgeDown)) {
-                mSpecifiedNudgeIdMap.put(FOCUS_DOWN,
-                        a.getResourceId(R.styleable.FocusArea_nudgeDown, View.NO_ID));
-            }
-
-            mDefaultFocusOverridesHistory = a.getBoolean(
-                    R.styleable.FocusArea_defaultFocusOverridesHistory, false);
-
-            mWrapAround = a.getBoolean(R.styleable.FocusArea_wrapAround, false);
-        } finally {
-            a.recycle();
-        }
+        mFocusAreaHelper = new FocusAreaHelper(this, attrs);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        if (mDefaultFocusId != View.NO_ID) {
-            mDefaultFocusView = requireViewById(mDefaultFocusId);
-        }
+        mFocusAreaHelper.onFinishInflate();
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-        if (mRtl != rtl) {
-            mRtl = rtl;
-
-            int temp = mPaddingLeft;
-            mPaddingLeft = mPaddingRight;
-            mPaddingRight = temp;
-
-            temp = mLeftOffset;
-            mLeftOffset = mRightOffset;
-            mRightOffset = temp;
-        }
+        mFocusAreaHelper.onLayout();
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        getViewTreeObserver().addOnGlobalFocusChangeListener(mFocusChangeListener);
+        mFocusAreaHelper.onAttachedToWindow();
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        getViewTreeObserver().removeOnGlobalFocusChangeListener(mFocusChangeListener);
+        mFocusAreaHelper.onDetachedFromWindow();
         super.onDetachedFromWindow();
     }
 
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
-        // To ensure the focus is initialized properly in rotary mode when there is a window focus
-        // change, this FocusArea will grab the focus if nothing is focused or the currently
-        // focused view's FocusLevel is lower than REGULAR_FOCUS.
-        if (hasWindowFocus && !isInTouchMode()) {
-            maybeInitFocus();
+        if (!mFocusAreaHelper.onWindowFocusChanged(hasWindowFocus)) {
+            super.onWindowFocusChanged(hasWindowFocus);
         }
-        super.onWindowFocusChanged(hasWindowFocus);
     }
 
-    /**
-     * Focuses on another view in this FocusArea if nothing is focused or the currently focused
-     * view's FocusLevel is lower than REGULAR_FOCUS.
-     */
-    private boolean maybeInitFocus() {
-        View root = getRootView();
-        View focus = root.findFocus();
-        return ViewUtils.initFocus(root, focus);
-    }
-
-    /**
-     * Focuses on a view in this FocusArea if the view is a better focus candidate than the
-     * currently focused view.
-     */
-    private boolean maybeAdjustFocus() {
-        View root = getRootView();
-        View focus = root.findFocus();
-        return ViewUtils.adjustFocus(root, focus);
-    }
-
-
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
-        switch (action) {
-            case ACTION_FOCUS:
-                // Repurpose ACTION_FOCUS to focus on its descendant. We can do this because
-                // FocusArea is not focusable and it didn't consume ACTION_FOCUS previously.
-                boolean success = focusOnDescendant();
-                if (success && mPreviousFocusArea != null) {
-                    int direction = getNudgeDirection(arguments);
-                    if (direction != INVALID_DIRECTION) {
-                        saveFocusAreaHistory(direction, mPreviousFocusArea, this,
-                                SystemClock.uptimeMillis());
-                    }
-                }
-                return success;
-            case ACTION_NUDGE_SHORTCUT:
-                return nudgeToShortcutView(arguments);
-            case ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA:
-                return nudgeToAnotherFocusArea(arguments);
-            default:
-                return super.performAccessibilityAction(action, arguments);
+        if (mFocusAreaHelper.isFocusAreaAction(action)) {
+            return mFocusAreaHelper.performAccessibilityAction(action, arguments);
         }
-    }
-
-    private boolean focusOnDescendant() {
-        View lastFocusedView = mRotaryCache.getFocusedView(SystemClock.uptimeMillis());
-        return ViewUtils.adjustFocus(this, lastFocusedView, mDefaultFocusOverridesHistory);
-    }
-
-    /**
-     * Gets the {@code app:defaultFocus} view.
-     *
-     * @hidden
-     */
-    @Nullable
-    public View getDefaultFocusView() {
-        return mDefaultFocusView;
-    }
-
-    private boolean nudgeToShortcutView(Bundle arguments) {
-        int direction = getNudgeDirection(arguments);
-        View targetView = getSpecifiedShortcut(direction);
-        if (targetView == null) {
-            // No nudge shortcut configured for the given direction.
-            return false;
-        }
-        if (targetView.isFocused()) {
-            // The nudge shortcut view is already focused; return false so that the user can
-            // nudge to another FocusArea.
-            return false;
-        }
-        return ViewUtils.requestFocus(targetView);
-    }
-
-    private boolean nudgeToAnotherFocusArea(Bundle arguments) {
-        int direction = getNudgeDirection(arguments);
-        long elapsedRealtime = SystemClock.uptimeMillis();
-
-        // Try to nudge to specified FocusArea, if any.
-        FocusArea targetFocusArea = getSpecifiedFocusArea(direction);
-        boolean success = targetFocusArea != null && targetFocusArea.focusOnDescendant();
-
-        // If failed, try to nudge to cached FocusArea, if any.
-        if (!success) {
-            targetFocusArea = mRotaryCache.getCachedFocusArea(direction, elapsedRealtime);
-            success = targetFocusArea != null && targetFocusArea.focusOnDescendant();
-        }
-
-        return success;
-    }
-
-    private static int getNudgeDirection(Bundle arguments) {
-        return arguments == null
-                ? INVALID_DIRECTION
-                : arguments.getInt(NUDGE_DIRECTION, INVALID_DIRECTION);
-    }
-
-    private void saveFocusAreaHistory(int direction, @NonNull FocusArea sourceFocusArea,
-            @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
-        // Save one-way rather than two-way nudge history to avoid infinite nudge loop.
-        if (sourceFocusArea.mRotaryCache.getCachedFocusArea(direction, elapsedRealtime) == null) {
-            // Save reversed nudge history so that the users can nudge back to where they were.
-            int oppositeDirection = getOppositeDirection(direction);
-            targetFocusArea.mRotaryCache.saveFocusArea(oppositeDirection, sourceFocusArea,
-                    elapsedRealtime);
-        }
-    }
-
-    /** Returns the direction opposite the given {@code direction} */
-    private static int getOppositeDirection(int direction) {
-        switch (direction) {
-            case View.FOCUS_LEFT:
-                return View.FOCUS_RIGHT;
-            case View.FOCUS_RIGHT:
-                return View.FOCUS_LEFT;
-            case View.FOCUS_UP:
-                return View.FOCUS_DOWN;
-            case View.FOCUS_DOWN:
-                return View.FOCUS_UP;
-            default: // fall out
-        }
-        throw new IllegalArgumentException("direction must be "
-                + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
-    }
-
-    @Nullable
-    private FocusArea getSpecifiedFocusArea(int direction) {
-        maybeInitializeSpecifiedFocusAreas();
-        return mSpecifiedNudgeFocusAreaMap.get(direction);
-    }
-
-    @Nullable
-    private View getSpecifiedShortcut(int direction) {
-        maybeInitializeSpecifiedShortcuts();
-        return mSpecifiedNudgeShortcutMap.get(direction);
+        return super.performAccessibilityAction(action, arguments);
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-
-        // Draw highlight on top of this FocusArea (including its background and content) but
-        // behind its children.
-        if (mEnableBackgroundHighlight && mHasFocus && !isInTouchMode()) {
-            mBackgroundHighlight.setBounds(
-                    mPaddingLeft + getScrollX(),
-                    mPaddingTop + getScrollY(),
-                    getScrollX() + getWidth() - mPaddingRight,
-                    getScrollY() + getHeight() - mPaddingBottom);
-            mBackgroundHighlight.draw(canvas);
-        }
+        mFocusAreaHelper.onDraw(canvas);
     }
 
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
-
-        // Draw highlight on top of this FocusArea (including its background and content) and its
-        // children (including background, content, focus highlight, etc).
-        if (mEnableForegroundHighlight && mHasFocus && !isInTouchMode()) {
-            mForegroundHighlight.setBounds(
-                    mPaddingLeft + getScrollX(),
-                    mPaddingTop + getScrollY(),
-                    getScrollX() + getWidth() - mPaddingRight,
-                    getScrollY() + getHeight() - mPaddingBottom);
-            mForegroundHighlight.draw(canvas);
-        }
+        mFocusAreaHelper.draw(canvas);
     }
 
     @Override
     public CharSequence getAccessibilityClassName() {
-        return FocusArea.class.getName();
+        return I_FOCUS_AREA_CLASS_NAME;
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
-        Bundle bundle = info.getExtras();
-        bundle.putInt(FOCUS_AREA_LEFT_BOUND_OFFSET, mLeftOffset);
-        bundle.putInt(FOCUS_AREA_RIGHT_BOUND_OFFSET, mRightOffset);
-        bundle.putInt(FOCUS_AREA_TOP_BOUND_OFFSET, mTopOffset);
-        bundle.putInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET, mBottomOffset);
+        mFocusAreaHelper.onInitializeAccessibilityNodeInfo(info);
     }
 
     @Override
@@ -712,127 +132,72 @@
         if (isInTouchMode()) {
             return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
         }
-        return maybeAdjustFocus();
+        return mFocusAreaHelper.onRequestFocusInDescendants();
     }
 
     @Override
     public boolean restoreDefaultFocus() {
-        return maybeAdjustFocus();
-    }
-
-    private void maybeInitializeSpecifiedFocusAreas() {
-        if (mSpecifiedNudgeFocusAreaMap != null) {
-            return;
-        }
-        View root = getRootView();
-        mSpecifiedNudgeFocusAreaMap = new SparseArray<>();
-        for (int direction : NUDGE_DIRECTIONS) {
-            int id = mSpecifiedNudgeIdMap.get(direction, View.NO_ID);
-            mSpecifiedNudgeFocusAreaMap.put(direction, root.findViewById(id));
-        }
-    }
-
-    private void maybeInitializeSpecifiedShortcuts() {
-        if (mSpecifiedNudgeShortcutMap != null) {
-            return;
-        }
-        View root = getRootView();
-        mSpecifiedNudgeShortcutMap = new SparseArray<>();
-        for (int direction : NUDGE_DIRECTIONS) {
-            int id = mSpecifiedNudgeShortcutIdMap.get(direction, View.NO_ID);
-            mSpecifiedNudgeShortcutMap.put(direction, root.findViewById(id));
-        }
-    }
-
-    /**
-     * Sets the padding (in pixels) of the FocusArea highlight.
-     * <p>
-     * It doesn't affect other values, such as the paddings on its child views.
-     */
-    public void setHighlightPadding(int left, int top, int right, int bottom) {
-        if (mPaddingLeft == left && mPaddingTop == top && mPaddingRight == right
-                && mPaddingBottom == bottom) {
-            return;
-        }
-        mPaddingLeft = left;
-        mPaddingTop = top;
-        mPaddingRight = right;
-        mPaddingBottom = bottom;
-        invalidate();
-    }
-
-    /**
-     * Sets the offset (in pixels) of the FocusArea's bounds.
-     * <p>
-     * It only affects the perceived bounds for the purposes of finding the nudge target. It doesn't
-     * affect the FocusArea's view bounds or highlight bounds. The offset should only be used when
-     * FocusAreas are overlapping and nudge interaction is ambiguous.
-     */
-    public void setBoundsOffset(int left, int top, int right, int bottom) {
-        mLeftOffset = left;
-        mTopOffset = top;
-        mRightOffset = right;
-        mBottomOffset = bottom;
-    }
-
-    /** Sets whether wrap-around is enabled for this FocusArea. */
-    public void setWrapAround(boolean wrapAround) {
-        mWrapAround = wrapAround;
-    }
-
-    /** Sets the default focus view in this FocusArea. */
-    public void setDefaultFocus(@NonNull View defaultFocus) {
-        mDefaultFocusView = defaultFocus;
-    }
-
-    /**
-     * Sets the nudge shortcut for the given {@code direction}. Removes the nudge shortcut if {@code
-     * view} is {@code null}.
-     */
-    public void setNudgeShortcut(int direction, @Nullable View view) {
-        if (!NUDGE_DIRECTIONS.contains(direction)) {
-            throw new IllegalArgumentException("direction must be "
-                    + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
-        }
-        maybeInitializeSpecifiedShortcuts();
-        if (view == null) {
-            mSpecifiedNudgeShortcutMap.remove(direction);
-        } else {
-            mSpecifiedNudgeShortcutMap.put(direction, view);
-        }
+        return mFocusAreaHelper.restoreDefaultFocus();
     }
 
     /**
      * @inheritDoc
      * <p>
-     * When {@link #mWrapAround} is true, the search is restricted to descendants of this
+     * When wrap-around is allowed, the search is restricted to descendants of this
      * {@link FocusArea}.
      */
     @Override
     public View focusSearch(View focused, int direction) {
-        if (mWrapAround) {
+        if (mFocusAreaHelper.isWrapAround()) {
             return FocusFinder.getInstance().findNextFocus(/* root= */ this, focused, direction);
         }
         return super.focusSearch(focused, direction);
     }
 
     @VisibleForTesting
-    void enableForegroundHighlight() {
-        mEnableForegroundHighlight = true;
+    @Override
+    @NonNull
+    public FocusAreaHelper getHelper() {
+        return mFocusAreaHelper;
     }
 
-    @VisibleForTesting
-    void setDefaultFocusOverridesHistory(boolean override) {
-        mDefaultFocusOverridesHistory = override;
+    @Override
+    public View getDefaultFocusView() {
+        return mFocusAreaHelper.getDefaultFocusView();
     }
 
-    @VisibleForTesting
-    void setRotaryCache(@NonNull RotaryCache rotaryCache) {
-        mRotaryCache = rotaryCache;
+    @Override
+    public void setDefaultFocus(@NonNull View defaultFocus) {
+        mFocusAreaHelper.setDefaultFocus(defaultFocus);
     }
 
-    @VisibleForTesting
-    void setClearFocusAreaHistoryWhenRotating(boolean clear) {
-        mClearFocusAreaHistoryWhenRotating = clear;
+    @Override
+    public void setHighlightPadding(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setHighlightPadding(left, top, right, bottom);
+    }
+
+    @Override
+    public void setBoundsOffset(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setBoundsOffset(left, top, right, bottom);
+    }
+
+    @Override
+    public void setWrapAround(boolean wrapAround) {
+        mFocusAreaHelper.setWrapAround(wrapAround);
+    }
+
+    @Override
+    public void setNudgeShortcut(int direction, @Nullable View view) {
+        mFocusAreaHelper.setNudgeShortcut(direction, view);
+    }
+
+    @Override
+    public void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target) {
+        mFocusAreaHelper.setNudgeTargetFocusArea(direction, target);
+    }
+
+    @Override
+    public void setDefaultFocusOverridesHistory(boolean override) {
+        mFocusAreaHelper.setDefaultFocusOverridesHistory(override);
     }
 }
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusAreaHelper.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusAreaHelper.java
new file mode 100644
index 0000000..2375dd7
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusAreaHelper.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import static android.view.View.FOCUS_DOWN;
+import static android.view.View.FOCUS_LEFT;
+import static android.view.View.FOCUS_RIGHT;
+import static android.view.View.FOCUS_UP;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_SHORTCUT;
+import static com.android.car.ui.utils.RotaryConstants.ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET;
+import static com.android.car.ui.utils.RotaryConstants.NUDGE_DIRECTION;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.ui.utils.ViewUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A helper class used by {@link IFocusArea} implementation classes such as {@link FocusArea}. */
+class FocusAreaHelper {
+
+    private static final String TAG = "FocusAreaHelper";
+
+    private static final int INVALID_DIMEN = -1;
+
+    private static final int INVALID_DIRECTION = -1;
+
+    private static final List<Integer> NUDGE_DIRECTIONS =
+            Arrays.asList(FOCUS_LEFT, FOCUS_RIGHT, FOCUS_UP, FOCUS_DOWN);
+
+    private static final List<Integer> FOCUS_AREA_ACTIONS =
+            Arrays.asList(ACTION_FOCUS, ACTION_NUDGE_SHORTCUT, ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA);
+
+    @NonNull
+    private final ViewGroup mFocusArea;
+
+    /**
+     * Whether one of {@link #mFocusArea}'s descendant is focused (the {@link #mFocusArea} itself
+     * is not focusable).
+     */
+    private boolean mHasFocus;
+
+    /**
+     * Whether to draw {@link #mForegroundHighlight} when one of {@link #mFocusArea}'s descendants
+     * is focused and it's not in touch mode.
+     */
+    private boolean mEnableForegroundHighlight;
+
+    /**
+     * Whether to draw {@link #mBackgroundHighlight} when one of {@link #mFocusArea}'s descendants
+     * is focused and it's not in touch mode.
+     */
+    private boolean mEnableBackgroundHighlight;
+
+    /**
+     * Highlight (typically outline of the focus area) drawn on top of {@link #mFocusArea} and its
+     * descendants.
+     */
+    private Drawable mForegroundHighlight;
+
+    /**
+     * Highlight (typically a solid or gradient shape) drawn on top of {@link #mFocusArea} but
+     * behind its descendants.
+     */
+    private Drawable mBackgroundHighlight;
+
+    /** The padding (in pixels) of the focus area highlight. */
+    private int mPaddingLeft;
+    private int mPaddingRight;
+    private int mPaddingTop;
+    private int mPaddingBottom;
+
+    /** The offset (in pixels) of {@link #mFocusArea}'s bounds. */
+    private int mLeftOffset;
+    private int mRightOffset;
+    private int mTopOffset;
+    private int mBottomOffset;
+
+    /** Whether the {@link #mFocusArea}'s layout direction is {@link View#LAYOUT_DIRECTION_RTL}. */
+    private boolean mRtl;
+
+    /** The ID of the view specified in {@link #mFocusArea}'s {@code app:defaultFocus}. */
+    private int mDefaultFocusId;
+    /** The view specified in {@link #mFocusArea}'s {@code app:defaultFocus}. */
+    @Nullable
+    private View mDefaultFocusView;
+
+    /**
+     * Whether to focus on the default focus view when nudging to {@link #mFocusArea}, even if
+     * there was another view in {@link #mFocusArea} focused before.
+     */
+    private boolean mDefaultFocusOverridesHistory;
+
+    /**
+     * Map from direction to nudge shortcut IDs specified in {@code app:nudgeLeftShortcut},
+     * {@code app:nudgRightShortcut}, {@code app:nudgeUpShortcut}, and {@code app
+     * :nudgeDownShortcut}.
+     */
+    private final SparseIntArray mSpecifiedNudgeShortcutIdMap = new SparseIntArray();
+
+    /** Map from direction to specified nudge shortcut views. */
+    private SparseArray<View> mSpecifiedNudgeShortcutMap;
+
+    /**
+     * Map from direction to nudge target focus area IDs specified in {@link #mFocusArea}'s
+     * {@code app:nudgeLeft}, {@code app:nudgRight}, {@code app:nudgeUp}, and {@code app:nudgeDown}.
+     */
+    private final SparseIntArray mSpecifiedNudgeIdMap = new SparseIntArray();
+
+    /** Map from direction to specified nudge target focus areas. */
+    private SparseArray<IFocusArea> mSpecifiedNudgeFocusAreaMap;
+
+    /** Whether wrap-around is enabled for {@link #mFocusArea}. */
+    private boolean mWrapAround;
+
+    /**
+     * Cache of focus history and nudge history of the rotary controller.
+     * <p>
+     * For focus history, the previously focused view and a timestamp will be saved when the
+     * focused view has changed.
+     * <p>
+     * For nudge history, the target focus area, direction, and a timestamp will be saved when the
+     * focus has moved from another focus area to {@link #mFocusArea}. There are two cases:
+     * <ul>
+     *     <li>The focus is moved to another focus area because {@link #mFocusArea} has called
+     *         {@link #nudgeToAnotherFocusArea}. In this case, the target focus area and direction
+     *         are trivial to {@link #mFocusArea}.
+     *     <li>The focus is moved to {@link #mFocusArea} because RotaryService has performed {@link
+     *         AccessibilityNodeInfo#ACTION_FOCUS} on {@link #mFocusArea}. In this case,
+     *         {@link #mFocusArea} can get the source focus area through the {@link
+     *         android.view.ViewTreeObserver.OnGlobalFocusChangeListener} registered, and can get
+     *         the direction when handling the action. Since the listener is triggered before
+     *         {@link View#requestFocus} returns (which is called when handling the action), the
+     *         source focus area is revealed earlier than the direction, so the nudge history should
+     *         be saved when the direction is revealed.
+     * </ul>
+     */
+    private RotaryCache mRotaryCache;
+
+    /** Whether to clear focus area history when the user rotates the rotary controller. */
+    private boolean mClearFocusAreaHistoryWhenRotating;
+
+    /** The focus area that had focus before {@link #mFocusArea}, if any. */
+    private IFocusArea mPreviousFocusArea;
+
+    /** The focused view in {@link #mFocusArea}, if any. */
+    private View mFocusedView;
+
+    private final OnGlobalFocusChangeListener mFocusChangeListener;
+
+    /**
+     * Whether to restore focus when Android frameworks want to focus inside {@link #mFocusArea}.
+     * This should be false if {@link #mFocusArea} is in a {@link com.android.wm.shell.TaskView}.
+     * The default value is true.
+     */
+    private boolean mShouldRestoreFocus = true;
+
+    FocusAreaHelper(@NonNull ViewGroup viewGroup, @Nullable AttributeSet attrs) {
+        mFocusArea = viewGroup;
+
+        mFocusChangeListener =
+                (oldFocus, newFocus) -> {
+                    boolean hasFocus = mFocusArea.hasFocus();
+                    saveFocusHistory(hasFocus);
+                    maybeUpdatePreviousFocusArea(hasFocus, oldFocus);
+                    maybeClearFocusAreaHistory(hasFocus, oldFocus);
+                    maybeUpdateFocusAreaHighlight(hasFocus);
+                    mHasFocus = hasFocus;
+                };
+
+        Context context = mFocusArea.getContext();
+        Resources resources = context.getResources();
+        mEnableForegroundHighlight = resources.getBoolean(
+                R.bool.car_ui_enable_focus_area_foreground_highlight);
+        mEnableBackgroundHighlight = resources.getBoolean(
+                R.bool.car_ui_enable_focus_area_background_highlight);
+        mForegroundHighlight = resources.getDrawable(
+                R.drawable.car_ui_focus_area_foreground_highlight, context.getTheme());
+        mBackgroundHighlight = resources.getDrawable(
+                R.drawable.car_ui_focus_area_background_highlight, context.getTheme());
+
+        mClearFocusAreaHistoryWhenRotating = resources.getBoolean(
+                R.bool.car_ui_clear_focus_area_history_when_rotating);
+
+        @RotaryCache.CacheType
+        int focusHistoryCacheType = resources.getInteger(R.integer.car_ui_focus_history_cache_type);
+        int focusHistoryExpirationPeriodMs =
+                resources.getInteger(R.integer.car_ui_focus_history_expiration_period_ms);
+        @RotaryCache.CacheType
+        int focusAreaHistoryCacheType = resources.getInteger(
+                R.integer.car_ui_focus_area_history_cache_type);
+        int focusAreaHistoryExpirationPeriodMs =
+                resources.getInteger(R.integer.car_ui_focus_area_history_expiration_period_ms);
+        mRotaryCache = new RotaryCache(focusHistoryCacheType, focusHistoryExpirationPeriodMs,
+                focusAreaHistoryCacheType, focusAreaHistoryExpirationPeriodMs);
+
+        // Ensure that an AccessibilityNodeInfo is created for mFocusArea.
+        mFocusArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+        // By default all ViewGroup subclasses do not call their draw() and onDraw() methods. We
+        // should enable it since we override these methods.
+        mFocusArea.setWillNotDraw(false);
+
+        initAttrs(context, attrs);
+    }
+
+    private void saveFocusHistory(boolean hasFocus) {
+        // Save focus history and clear mFocusedView if focus is leaving mFocusArea.
+        if (!hasFocus) {
+            if (mHasFocus) {
+                mRotaryCache.saveFocusedView(mFocusedView, SystemClock.uptimeMillis());
+                mFocusedView = null;
+            }
+            return;
+        }
+
+        // Update mFocusedView if a descendant of mFocusArea is focused.
+        View v = mFocusArea.getFocusedChild();
+        while (v != null) {
+            if (v.isFocused()) {
+                break;
+            }
+            v = v instanceof ViewGroup ? ((ViewGroup) v).getFocusedChild() : null;
+        }
+        mFocusedView = v;
+    }
+
+    /**
+     * Updates {@link #mPreviousFocusArea} when the focus has moved from another focus area to
+     * {@link #mFocusArea}, and sets it to {@code null} in any other cases.
+     */
+    private void maybeUpdatePreviousFocusArea(boolean hasFocus, View oldFocus) {
+        if (mHasFocus || !hasFocus || oldFocus == null || oldFocus instanceof FocusParkingView) {
+            mPreviousFocusArea = null;
+            return;
+        }
+        mPreviousFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
+        if (mPreviousFocusArea == null) {
+            Log.w(TAG, "No ancestor focus area for " + oldFocus);
+        }
+    }
+
+    /**
+     * Clears focus area nudge history when the user rotates the controller to move focus within
+     * {@link #mFocusArea}.
+     */
+    private void maybeClearFocusAreaHistory(boolean hasFocus, View oldFocus) {
+        if (!mClearFocusAreaHistoryWhenRotating) {
+            return;
+        }
+        if (!hasFocus || oldFocus == null) {
+            return;
+        }
+        IFocusArea oldFocusArea = ViewUtils.getAncestorFocusArea(oldFocus);
+        if (oldFocusArea != mFocusArea) {
+            return;
+        }
+        mRotaryCache.clearFocusAreaHistory();
+    }
+
+    /** Updates highlight of {@link #mFocusArea} if it has gained or lost focus. */
+    private void maybeUpdateFocusAreaHighlight(boolean hasFocus) {
+        if (!mEnableBackgroundHighlight && !mEnableForegroundHighlight) {
+            return;
+        }
+        if (mHasFocus != hasFocus) {
+            mFocusArea.invalidate();
+        }
+    }
+
+    private void initAttrs(Context context, @Nullable AttributeSet attrs) {
+        if (attrs == null) {
+            return;
+        }
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IFocusArea);
+        try {
+            mDefaultFocusId = a.getResourceId(R.styleable.IFocusArea_defaultFocus, View.NO_ID);
+
+            // Initialize the highlight padding. The padding, for example, left padding, is set in
+            // the following order:
+            // 1. if highlightPaddingStart (or highlightPaddingEnd in RTL layout) specified, use it
+            // 2. otherwise, if highlightPaddingHorizontal is specified, use it
+            // 3. otherwise use 0
+
+            int paddingStart = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_highlightPaddingStart, INVALID_DIMEN);
+            if (paddingStart == INVALID_DIMEN) {
+                paddingStart = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_highlightPaddingHorizontal, 0);
+            }
+
+            int paddingEnd = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_highlightPaddingEnd, INVALID_DIMEN);
+            if (paddingEnd == INVALID_DIMEN) {
+                paddingEnd = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_highlightPaddingHorizontal, 0);
+            }
+
+            mRtl = mFocusArea.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            mPaddingLeft = mRtl ? paddingEnd : paddingStart;
+            mPaddingRight = mRtl ? paddingStart : paddingEnd;
+
+            mPaddingTop = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_highlightPaddingTop, INVALID_DIMEN);
+            if (mPaddingTop == INVALID_DIMEN) {
+                mPaddingTop = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_highlightPaddingVertical, 0);
+            }
+
+            mPaddingBottom = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_highlightPaddingBottom, INVALID_DIMEN);
+            if (mPaddingBottom == INVALID_DIMEN) {
+                mPaddingBottom = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_highlightPaddingVertical, 0);
+            }
+
+            // Initialize the offset of mFocusArea's bounds. The offset, for example, left
+            // offset, is set in the following order:
+            // 1. if startBoundOffset (or endBoundOffset in RTL layout) specified, use it
+            // 2. otherwise, if horizontalBoundOffset is specified, use it
+            // 3. otherwise use mPaddingLeft
+
+            int startOffset = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_startBoundOffset, INVALID_DIMEN);
+            if (startOffset == INVALID_DIMEN) {
+                startOffset = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_horizontalBoundOffset, paddingStart);
+            }
+
+            int endOffset = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_endBoundOffset, INVALID_DIMEN);
+            if (endOffset == INVALID_DIMEN) {
+                endOffset = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_horizontalBoundOffset, paddingEnd);
+            }
+
+            mLeftOffset = mRtl ? endOffset : startOffset;
+            mRightOffset = mRtl ? startOffset : endOffset;
+
+            mTopOffset = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_topBoundOffset, INVALID_DIMEN);
+            if (mTopOffset == INVALID_DIMEN) {
+                mTopOffset = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_verticalBoundOffset, mPaddingTop);
+            }
+
+            mBottomOffset = a.getDimensionPixelSize(
+                    R.styleable.IFocusArea_bottomBoundOffset, INVALID_DIMEN);
+            if (mBottomOffset == INVALID_DIMEN) {
+                mBottomOffset = a.getDimensionPixelSize(
+                        R.styleable.IFocusArea_verticalBoundOffset, mPaddingBottom);
+            }
+
+            // Handle new nudge shortcut attributes.
+            if (a.hasValue(R.styleable.IFocusArea_nudgeLeftShortcut)) {
+                mSpecifiedNudgeShortcutIdMap.put(FOCUS_LEFT,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeLeftShortcut, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeRightShortcut)) {
+                mSpecifiedNudgeShortcutIdMap.put(FOCUS_RIGHT,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeRightShortcut, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeUpShortcut)) {
+                mSpecifiedNudgeShortcutIdMap.put(FOCUS_UP,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeUpShortcut, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeDownShortcut)) {
+                mSpecifiedNudgeShortcutIdMap.put(FOCUS_DOWN,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeDownShortcut, View.NO_ID));
+            }
+
+            // Handle legacy nudge shortcut attributes.
+            int nudgeShortcutId = a.getResourceId(R.styleable.IFocusArea_nudgeShortcut, View.NO_ID);
+            int nudgeShortcutDirection = a.getInt(
+                    R.styleable.IFocusArea_nudgeShortcutDirection, INVALID_DIRECTION);
+            if ((nudgeShortcutId == View.NO_ID) ^ (nudgeShortcutDirection == INVALID_DIRECTION)) {
+                throw new IllegalStateException("nudgeShortcut and nudgeShortcutDirection must "
+                        + "be specified together");
+            }
+            if (nudgeShortcutId != View.NO_ID) {
+                if (mSpecifiedNudgeShortcutIdMap.size() > 0) {
+                    throw new IllegalStateException(
+                            "Don't use nudgeShortcut/nudgeShortcutDirection and nudge*Shortcut in "
+                                    + "the same focus area. Use nudge*Shortcut only.");
+                }
+                mSpecifiedNudgeShortcutIdMap.put(nudgeShortcutDirection, nudgeShortcutId);
+            }
+
+            // Handle nudge targets.
+            if (a.hasValue(R.styleable.IFocusArea_nudgeLeft)) {
+                mSpecifiedNudgeIdMap.put(FOCUS_LEFT,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeLeft, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeRight)) {
+                mSpecifiedNudgeIdMap.put(FOCUS_RIGHT,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeRight, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeUp)) {
+                mSpecifiedNudgeIdMap.put(FOCUS_UP,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeUp, View.NO_ID));
+            }
+            if (a.hasValue(R.styleable.IFocusArea_nudgeDown)) {
+                mSpecifiedNudgeIdMap.put(FOCUS_DOWN,
+                        a.getResourceId(R.styleable.IFocusArea_nudgeDown, View.NO_ID));
+            }
+
+            mDefaultFocusOverridesHistory = a.getBoolean(
+                    R.styleable.IFocusArea_defaultFocusOverridesHistory, false);
+
+            mWrapAround = a.getBoolean(R.styleable.IFocusArea_wrapAround, false);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    void onFinishInflate() {
+        if (mDefaultFocusId != View.NO_ID) {
+            mDefaultFocusView = mFocusArea.requireViewById(mDefaultFocusId);
+        }
+    }
+
+    void onLayout() {
+        boolean rtl = mFocusArea.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        if (mRtl != rtl) {
+            mRtl = rtl;
+
+            int temp = mPaddingLeft;
+            mPaddingLeft = mPaddingRight;
+            mPaddingRight = temp;
+
+            temp = mLeftOffset;
+            mLeftOffset = mRightOffset;
+            mRightOffset = temp;
+        }
+    }
+
+    void onAttachedToWindow() {
+        mFocusArea.getViewTreeObserver().addOnGlobalFocusChangeListener(mFocusChangeListener);
+
+        // Disable restore focus behavior if mFocusArea is in a TaskView.
+        if (mShouldRestoreFocus && ViewUtils.isInMultiWindowMode(mFocusArea)) {
+            mShouldRestoreFocus = false;
+        }
+    }
+
+    void onDetachedFromWindow() {
+        mFocusArea.getViewTreeObserver().removeOnGlobalFocusChangeListener(mFocusChangeListener);
+    }
+
+    boolean onWindowFocusChanged(boolean hasWindowFocus) {
+        // TODO(b/201700195): sometimes onWindowFocusChanged() won't be called when window focus
+        //  has changed, so add the log for debugging.
+        Log.d(TAG, "The window of Activity ["
+                + ViewUtils.findActivity(mFocusArea.getContext())
+                + (hasWindowFocus ? "] gained" : "] lost") + " focus");
+        // To ensure the focus is initialized properly in rotary mode when there is a window focus
+        // change, mFocusArea will grab the focus if nothing is focused or the currently
+        // focused view's FocusLevel is lower than REGULAR_FOCUS.
+        if (hasWindowFocus && mShouldRestoreFocus && !mFocusArea.isInTouchMode()) {
+            maybeInitFocus();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Focuses on another view in {@link #mFocusArea} if nothing is focused or the currently focused
+     * view's FocusLevel is lower than REGULAR_FOCUS.
+     */
+    private boolean maybeInitFocus() {
+        View root = mFocusArea.getRootView();
+        View focus = root.findFocus();
+        return ViewUtils.initFocus(root, focus);
+    }
+
+    /**
+     * Focuses on a view in {@link #mFocusArea} if the view is a better focus candidate than the
+     * currently focused view.
+     */
+    private boolean maybeAdjustFocus() {
+        View root = mFocusArea.getRootView();
+        View focus = root.findFocus();
+        return ViewUtils.adjustFocus(root, focus);
+    }
+
+    /** Whether the given {@code action} is custom action for {@link IFocusArea} subclasses. */
+    boolean isFocusAreaAction(int action) {
+        return FOCUS_AREA_ACTIONS.contains(action);
+    }
+
+    boolean performAccessibilityAction(int action, Bundle arguments) {
+        switch (action) {
+            case ACTION_FOCUS:
+                // Repurpose ACTION_FOCUS to focus on mFocusArea's descendant. We can do this
+                // because mFocusArea is not focusable and it didn't consume
+                // ACTION_FOCUS previously.
+                boolean success = focusOnDescendant();
+                if (success && mPreviousFocusArea != null) {
+                    int direction = getNudgeDirection(arguments);
+                    if (direction != INVALID_DIRECTION) {
+                        saveFocusAreaHistory(direction, mPreviousFocusArea,
+                                (IFocusArea) mFocusArea, SystemClock.uptimeMillis());
+                    }
+                }
+                return success;
+            case ACTION_NUDGE_SHORTCUT:
+                return nudgeToShortcutView(arguments);
+            case ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA:
+                return nudgeToAnotherFocusArea(arguments);
+            default:
+                return false;
+        }
+    }
+
+    private boolean focusOnDescendant() {
+        View lastFocusedView = mRotaryCache.getFocusedView(SystemClock.uptimeMillis());
+        return ViewUtils.adjustFocus(mFocusArea, lastFocusedView, mDefaultFocusOverridesHistory);
+    }
+
+    private boolean nudgeToShortcutView(Bundle arguments) {
+        int direction = getNudgeDirection(arguments);
+        View targetView = getSpecifiedShortcut(direction);
+        if (targetView == null) {
+            // No nudge shortcut configured for the given direction.
+            return false;
+        }
+        if (targetView.isFocused()) {
+            // The nudge shortcut view is already focused; return false so that the user can
+            // nudge to another focus area.
+            return false;
+        }
+        return ViewUtils.requestFocus(targetView);
+    }
+
+    private boolean nudgeToAnotherFocusArea(Bundle arguments) {
+        int direction = getNudgeDirection(arguments);
+        long elapsedRealtime = SystemClock.uptimeMillis();
+
+        // Try to nudge to specified focus area, if any.
+        IFocusArea targetFocusArea = getSpecifiedFocusArea(direction);
+        boolean success =
+                targetFocusArea != null && targetFocusArea.getHelper().focusOnDescendant();
+
+        // If failed, try to nudge to cached focus area, if any.
+        if (!success) {
+            targetFocusArea = mRotaryCache.getCachedFocusArea(direction, elapsedRealtime);
+            success = targetFocusArea != null && targetFocusArea.getHelper().focusOnDescendant();
+        }
+
+        return success;
+    }
+
+    private static int getNudgeDirection(Bundle arguments) {
+        return arguments == null
+                ? INVALID_DIRECTION
+                : arguments.getInt(NUDGE_DIRECTION, INVALID_DIRECTION);
+    }
+
+    private void saveFocusAreaHistory(int direction, @NonNull IFocusArea sourceFocusArea,
+            @NonNull IFocusArea targetFocusArea, long elapsedRealtime) {
+        // Save one-way rather than two-way nudge history to avoid infinite nudge loop.
+        FocusAreaHelper sourceHelper = sourceFocusArea.getHelper();
+        if (sourceHelper.getCachedFocusArea(direction, elapsedRealtime) == null) {
+            // Save reversed nudge history so that the users can nudge back to where they were.
+            int oppositeDirection = getOppositeDirection(direction);
+            FocusAreaHelper targetHelper = targetFocusArea.getHelper();
+            targetHelper.saveFocusArea(oppositeDirection, sourceFocusArea, elapsedRealtime);
+        }
+    }
+
+    @Nullable
+    IFocusArea getCachedFocusArea(int direction, long elapsedRealtime) {
+        return mRotaryCache.getCachedFocusArea(direction, elapsedRealtime);
+    }
+
+    /** Saves the focus area nudge history. */
+    void saveFocusArea(int direction, @NonNull IFocusArea targetFocusArea, long elapsedRealtime) {
+        mRotaryCache.saveFocusArea(direction, targetFocusArea, elapsedRealtime);
+    }
+
+    /** Returns the direction opposite the given {@code direction} */
+    @VisibleForTesting
+    private static int getOppositeDirection(int direction) {
+        switch (direction) {
+            case FOCUS_LEFT:
+                return FOCUS_RIGHT;
+            case FOCUS_RIGHT:
+                return FOCUS_LEFT;
+            case FOCUS_UP:
+                return FOCUS_DOWN;
+            case FOCUS_DOWN:
+                return FOCUS_UP;
+        }
+        throw new IllegalArgumentException("direction must be "
+                + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
+    }
+
+    @Nullable
+    private IFocusArea getSpecifiedFocusArea(int direction) {
+        maybeInitializeSpecifiedFocusAreas();
+        return mSpecifiedNudgeFocusAreaMap.get(direction);
+    }
+
+    @Nullable
+    private View getSpecifiedShortcut(int direction) {
+        maybeInitializeSpecifiedShortcuts();
+        return mSpecifiedNudgeShortcutMap.get(direction);
+    }
+
+    void onDraw(Canvas canvas) {
+        // Draw highlight on top of mFocusArea (including its background and content) but
+        // behind its children.
+        if (mEnableBackgroundHighlight && mHasFocus && !mFocusArea.isInTouchMode()) {
+            mBackgroundHighlight.setBounds(
+                    mPaddingLeft + mFocusArea.getScrollX(),
+                    mPaddingTop + mFocusArea.getScrollY(),
+                    mFocusArea.getScrollX() + mFocusArea.getWidth() - mPaddingRight,
+                    mFocusArea.getScrollY() + mFocusArea.getHeight() - mPaddingBottom);
+            mBackgroundHighlight.draw(canvas);
+        }
+    }
+
+    void draw(Canvas canvas) {
+        // Draw highlight on top of mFocusArea (including its background and content) and its
+        // children (including background, content, focus highlight, etc).
+        if (mEnableForegroundHighlight && mHasFocus && !mFocusArea.isInTouchMode()) {
+            mForegroundHighlight.setBounds(
+                    mPaddingLeft + mFocusArea.getScrollX(),
+                    mPaddingTop + mFocusArea.getScrollY(),
+                    mFocusArea.getScrollX() + mFocusArea.getWidth() - mPaddingRight,
+                    mFocusArea.getScrollY() + mFocusArea.getHeight() - mPaddingBottom);
+            mForegroundHighlight.draw(canvas);
+        }
+    }
+
+    void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        Bundle bundle = info.getExtras();
+        bundle.putInt(FOCUS_AREA_LEFT_BOUND_OFFSET, mLeftOffset);
+        bundle.putInt(FOCUS_AREA_RIGHT_BOUND_OFFSET, mRightOffset);
+        bundle.putInt(FOCUS_AREA_TOP_BOUND_OFFSET, mTopOffset);
+        bundle.putInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET, mBottomOffset);
+    }
+
+    boolean onRequestFocusInDescendants() {
+        if (!mShouldRestoreFocus) {
+            return false;
+        }
+        return maybeAdjustFocus();
+    }
+
+    boolean restoreDefaultFocus() {
+        if (!mShouldRestoreFocus) {
+            return false;
+        }
+        return maybeAdjustFocus();
+    }
+
+    private void maybeInitializeSpecifiedFocusAreas() {
+        if (mSpecifiedNudgeFocusAreaMap != null) {
+            return;
+        }
+        View root = mFocusArea.getRootView();
+        mSpecifiedNudgeFocusAreaMap = new SparseArray<>();
+        for (int direction : NUDGE_DIRECTIONS) {
+            int id = mSpecifiedNudgeIdMap.get(direction, View.NO_ID);
+            mSpecifiedNudgeFocusAreaMap.put(direction, root.findViewById(id));
+        }
+    }
+
+    private void maybeInitializeSpecifiedShortcuts() {
+        if (mSpecifiedNudgeShortcutMap != null) {
+            return;
+        }
+        View root = mFocusArea.getRootView();
+        mSpecifiedNudgeShortcutMap = new SparseArray<>();
+        for (int direction : NUDGE_DIRECTIONS) {
+            int id = mSpecifiedNudgeShortcutIdMap.get(direction, View.NO_ID);
+            mSpecifiedNudgeShortcutMap.put(direction, root.findViewById(id));
+        }
+    }
+
+    /** Gets the default focus view in {@link #mFocusArea}. */
+    View getDefaultFocusView() {
+        return mDefaultFocusView;
+    }
+
+    /** Sets the default focus view in {@link #mFocusArea}. */
+    void setDefaultFocus(@NonNull View defaultFocus) {
+        mDefaultFocusView = defaultFocus;
+    }
+
+    /**
+     * Sets the padding (in pixels) of the focus area highlight.
+     * <p>
+     * It doesn't affect other values, such as the paddings on {@link #mFocusArea}'s child views.
+     */
+    void setHighlightPadding(int left, int top, int right, int bottom) {
+        if (mPaddingLeft == left && mPaddingTop == top && mPaddingRight == right
+                && mPaddingBottom == bottom) {
+            return;
+        }
+        mPaddingLeft = left;
+        mPaddingTop = top;
+        mPaddingRight = right;
+        mPaddingBottom = bottom;
+        mFocusArea.invalidate();
+    }
+
+    /**
+     * Sets the offset (in pixels) of {@link #mFocusArea}'s perceived bounds.
+     * <p>
+     * It only affects the perceived bounds for the purposes of finding the nudge target. It doesn't
+     * affect {@link #mFocusArea}'s view bounds or highlight bounds. The offset should only be used
+     * when focus areas are overlapping and nudge interaction is ambiguous.
+     */
+    void setBoundsOffset(int left, int top, int right, int bottom) {
+        mLeftOffset = left;
+        mTopOffset = top;
+        mRightOffset = right;
+        mBottomOffset = bottom;
+    }
+
+    /** Whether wrap-around is enabled for {@link #mFocusArea}. */
+    boolean isWrapAround() {
+        return mWrapAround;
+    }
+
+    /** Sets whether wrap-around is enabled for {@link #mFocusArea}. */
+    void setWrapAround(boolean wrapAround) {
+        mWrapAround = wrapAround;
+    }
+
+    /**
+     * Sets the nudge shortcut for the given {@code direction}. Removes the nudge shortcut if
+     * {@code view} is {@code null}.
+     */
+    void setNudgeShortcut(int direction, @Nullable View view) {
+        if (!NUDGE_DIRECTIONS.contains(direction)) {
+            throw new IllegalArgumentException("direction must be "
+                    + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
+        }
+        maybeInitializeSpecifiedShortcuts();
+        if (view == null) {
+            mSpecifiedNudgeShortcutMap.remove(direction);
+        } else {
+            mSpecifiedNudgeShortcutMap.put(direction, view);
+        }
+    }
+
+    /**
+     * Sets the nudge target focus area for the given {@code direction}. Removes the existing
+     * target if {@code target} is {@code null}.
+     */
+    void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target) {
+        if (!NUDGE_DIRECTIONS.contains(direction)) {
+            throw new IllegalArgumentException("direction must be "
+                    + "FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, or FOCUS_RIGHT.");
+        }
+        maybeInitializeSpecifiedFocusAreas();
+        if (target == null) {
+            mSpecifiedNudgeFocusAreaMap.remove(direction);
+        } else {
+            mSpecifiedNudgeFocusAreaMap.put(direction, target);
+        }
+    }
+
+    void setDefaultFocusOverridesHistory(boolean override) {
+        mDefaultFocusOverridesHistory = override;
+    }
+
+    @VisibleForTesting
+    void enableForegroundHighlight() {
+        mEnableForegroundHighlight = true;
+    }
+
+    @VisibleForTesting
+    void setRotaryCache(@NonNull RotaryCache rotaryCache) {
+        mRotaryCache = rotaryCache;
+    }
+
+    @VisibleForTesting
+    void setClearFocusAreaHistoryWhenRotating(boolean clear) {
+        mClearFocusAreaHistoryWhenRotating = clear;
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusParkingView.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusParkingView.java
index ab42857..d1bb5ff 100644
--- a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusParkingView.java
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FocusParkingView.java
@@ -18,6 +18,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
 
+import static com.android.car.ui.utils.RotaryConstants.ACTION_DISMISS_POPUP_WINDOW;
 import static com.android.car.ui.utils.RotaryConstants.ACTION_HIDE_IME;
 import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS;
 
@@ -28,6 +29,7 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
@@ -61,7 +63,7 @@
  * Then it will avoid the wrap-around by not moving focus.
  * <p>
  * To ensure the focus is initialized properly when there is a window change, the FocusParkingView
- * will not get focused when the framework wants to focus on it. Instead, it will try to find a
+ * will not get focused when Android frameworks want to focus on it. Instead, it will try to find a
  * better focus target in the window and focus on the target. That said, the FocusParkingView can
  * still be focused in order to clear focus highlight in the window, such as when RotaryService
  * performs {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_FOCUS} on the
@@ -69,6 +71,17 @@
  */
 public class FocusParkingView extends View {
 
+    private static final String TAG = "FocusParkingView";
+    private static final boolean DEBUG = false;
+
+    /** Interface used to dismiss a popup window. */
+    public interface OnDismissPopupWindow {
+        void onDismiss();
+    }
+
+    @Nullable
+    private OnDismissPopupWindow mOnDismissPopupWindow;
+
     /**
      * The focused view in the window containing this FocusParkingView. It's null if no view is
      * focused, or the focused view is a FocusParkingView.
@@ -84,9 +97,9 @@
     ViewGroup mScrollableContainer;
 
     /**
-     * Whether to restore focus when the frameworks wants to focus this view. When false, this view
-     * allows itself to be focused instead. This should be false for the {@code FocusParkingView} in
-     * a {@code TaskView}. The default value is true.
+     * Whether to restore focus when Android frameworks want to focus this view. When false, this
+     * view allows itself to be focused instead. This should be false if this view is in a
+     * {@link com.android.wm.shell.TaskView}. The default value is true.
      */
     private boolean mShouldRestoreFocus = true;
 
@@ -99,6 +112,12 @@
 
     private final OnGlobalFocusChangeListener mFocusChangeListener =
             (oldFocus, newFocus) -> {
+                if (DEBUG) {
+                    Log.d(TAG, "oldFocus:" + oldFocus + ", newFocus:" + newFocus);
+                    for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
+                        Log.d(TAG, ste.toString());
+                    }
+                }
                 // Keep track of the focused view so that we can recover focus when it's removed.
                 View focusedView = newFocus instanceof FocusParkingView ? null : newFocus;
                 updateFocusedView(focusedView);
@@ -179,6 +198,10 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         getViewTreeObserver().addOnGlobalFocusChangeListener(mFocusChangeListener);
+        // Disable restore focus behavior if this view is in a TaskView.
+        if (mShouldRestoreFocus && ViewUtils.isInMultiWindowMode(this)) {
+            mShouldRestoreFocus = false;
+        }
     }
 
     @Override
@@ -189,6 +212,11 @@
 
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
+        // TODO(b/201700195): sometimes onWindowFocusChanged() won't be called when window focus
+        //  has changed, so add the log for debugging.
+        Log.d(TAG, "The window of Activity [" + ViewUtils.findActivity(getContext())
+                    + (hasWindowFocus ? "] gained" : "] lost") + " focus");
+
         if (!hasWindowFocus) {
             // We need to clear the focus highlight(by parking the focus on the FocusParkingView)
             // once the current window goes to background. This can't be done by RotaryService
@@ -200,7 +228,7 @@
             // OnGlobalFocusChangeListener won't be triggered when the window lost focus, so reset
             // the focused view here.
             updateFocusedView(null);
-        } else if (isFocused()) {
+        } else if (isFocused() && mShouldRestoreFocus) {
             // When FocusParkingView is focused and the window just gets focused, transfer the view
             // focus to a non-FocusParkingView in the window.
             restoreFocusInRoot(/* checkForTouchMode= */ true);
@@ -229,6 +257,12 @@
                     return super.requestFocus(FOCUS_DOWN, null);
                 }
                 return false;
+            case ACTION_DISMISS_POPUP_WINDOW:
+                if (mOnDismissPopupWindow != null) {
+                    mOnDismissPopupWindow.onDismiss();
+                    return true;
+                }
+                return false;
         }
         return super.performAccessibilityAction(action, arguments);
     }
@@ -238,8 +272,8 @@
         if (!mShouldRestoreFocus) {
             return super.requestFocus(direction, previouslyFocusedRect);
         }
-        // Find a better target to focus instead of focusing this FocusParkingView when the
-        // framework wants to focus it.
+        // Find a better target to focus instead of focusing this FocusParkingView when
+        // Android frameworks want to focus it.
         return restoreFocusInRoot(/* checkForTouchMode= */ true);
     }
 
@@ -254,14 +288,22 @@
     }
 
     /**
-     * Sets whether this view should restore focus when the framework wants to focus this view. When
-     * set to false, this view allows itself to be focused instead. This should be set to false for
-     * the {@code FocusParkingView} in a {@code TaskView}.  The default value is true.
+     * Sets whether this view should restore focus when Android frameworks want to focus this view.
+     * When set to false, this view allows itself to be focused instead. This should be set to false
+     * for the {@code FocusParkingView} in a {@code TaskView}.  The default value is true.
      */
     public void setShouldRestoreFocus(boolean shouldRestoreFocus) {
         mShouldRestoreFocus = shouldRestoreFocus;
     }
 
+    /**
+     * Sets an {@link OnDismissPopupWindow} callback. Removes the existing callback if
+     * {@code onDismissPopupWindow} is null.
+     */
+    public void setOnPopupWindowDismiss(@Nullable OnDismissPopupWindow onDismissPopupWindow) {
+        mOnDismissPopupWindow = onDismissPopupWindow;
+    }
+
     private boolean restoreFocusInRoot(boolean checkForTouchMode) {
         // Don't do anything in touch mode if checkForTouchMode is true.
         if (checkForTouchMode && isInTouchMode()) {
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FrameFocusArea.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FrameFocusArea.java
new file mode 100644
index 0000000..b8e4cb0
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/FrameFocusArea.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import static com.android.car.ui.utils.RotaryConstants.I_FOCUS_AREA_CLASS_NAME;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.FocusFinder;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * A {@link FrameLayout} used as a navigation block for the rotary controller.
+ * <p>
+ * When creating a navigation block in the layout file, if you intend to use a FrameLayout as a
+ * container for that block, just use a FrameFocusArea instead; otherwise use other
+ * {@link IFocusArea} implementations, such as {@link FocusArea} which extends
+ * {@link android.widget.LinearLayout}.
+ * <p>
+ * DO NOT nest an IFocusArea inside another IFocusArea because it will result in undefined
+ * navigation behavior.
+ */
+public class FrameFocusArea extends FrameLayout implements IFocusArea {
+
+    @NonNull
+    private final FocusAreaHelper mFocusAreaHelper;
+
+    public FrameFocusArea(Context context) {
+        this(context, null);
+    }
+
+    public FrameFocusArea(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FrameFocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public FrameFocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mFocusAreaHelper = new FocusAreaHelper(this, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFocusAreaHelper.onFinishInflate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mFocusAreaHelper.onLayout();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mFocusAreaHelper.onAttachedToWindow();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mFocusAreaHelper.onDetachedFromWindow();
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!mFocusAreaHelper.onWindowFocusChanged(hasWindowFocus())) {
+            super.onWindowFocusChanged(hasWindowFocus);
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (mFocusAreaHelper.isFocusAreaAction(action)) {
+            return mFocusAreaHelper.performAccessibilityAction(action, arguments);
+        }
+        return super.performAccessibilityAction(action, arguments);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mFocusAreaHelper.onDraw(canvas);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        mFocusAreaHelper.draw(canvas);
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return I_FOCUS_AREA_CLASS_NAME;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        mFocusAreaHelper.onInitializeAccessibilityNodeInfo(info);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        if (isInTouchMode()) {
+            return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+        }
+        return mFocusAreaHelper.onRequestFocusInDescendants();
+    }
+
+    @Override
+    public boolean restoreDefaultFocus() {
+        return mFocusAreaHelper.restoreDefaultFocus();
+    }
+
+    /**
+     * @inheritDoc
+     * <p>
+     * When wrap-around is allowed, the search is restricted to descendants of this
+     * {@link FrameFocusArea}.
+     */
+    @Override
+    public View focusSearch(View focused, int direction) {
+        if (mFocusAreaHelper.isWrapAround()) {
+            return FocusFinder.getInstance().findNextFocus(/* root= */ this, focused, direction);
+        }
+        return super.focusSearch(focused, direction);
+    }
+
+    @VisibleForTesting
+    @Override
+    @NonNull
+    public FocusAreaHelper getHelper() {
+        return mFocusAreaHelper;
+    }
+
+    @Override
+    public View getDefaultFocusView() {
+        return mFocusAreaHelper.getDefaultFocusView();
+    }
+
+    @Override
+    public void setDefaultFocus(@NonNull View defaultFocus) {
+        mFocusAreaHelper.setDefaultFocus(defaultFocus);
+    }
+
+    @Override
+    public void setHighlightPadding(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setHighlightPadding(left, top, right, bottom);
+    }
+
+    @Override
+    public void setBoundsOffset(int left, int top, int right, int bottom) {
+        mFocusAreaHelper.setBoundsOffset(left, top, right, bottom);
+    }
+
+    @Override
+    public void setWrapAround(boolean wrapAround) {
+        mFocusAreaHelper.setWrapAround(wrapAround);
+    }
+
+    @Override
+    public void setNudgeShortcut(int direction, @Nullable View view) {
+        mFocusAreaHelper.setNudgeShortcut(direction, view);
+    }
+
+    @Override
+    public void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target) {
+        mFocusAreaHelper.setNudgeTargetFocusArea(direction, target);
+    }
+
+    @Override
+    public void setDefaultFocusOverridesHistory(boolean override) {
+        mFocusAreaHelper.setDefaultFocusOverridesHistory(override);
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/IFocusArea.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/IFocusArea.java
new file mode 100644
index 0000000..0e6a884
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/IFocusArea.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * An interface used for rotary controller navigation. When a class implements this interface and
+ * extends a subclass of {@link android.view.ViewGroup}, the class can be used as a navigation
+ * block for the rotary controller. For example, the {@link FocusArea} class, which extends
+ * {@link android.widget.LinearLayout}.
+ * <p>
+ * The {@link com.android.car.rotary.RotaryService} looks for instances of IFocusArea in
+ * the view hierarchy when handling rotate and nudge actions. When receiving a rotation event
+ * ({@link android.car.input.RotaryEvent}), RotaryService will move the focus to another
+ * {@link View} that can take focus within the same IFocusArea. When receiving a nudge event
+ * ({@link android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_UP},
+ * {@link android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_DOWN},
+ * {@link android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_LEFT}, or
+ * {@link android.view.KeyEvent#KEYCODE_SYSTEM_NAVIGATION_RIGHT}),
+ * RotaryService will move the focus to another view that can take focus in another (typically
+ * adjacent) IFocusArea.
+ * <p>
+ * If enabled, IFocusArea can draw highlights when one of its descendants has focus and it's not in
+ * touch mode.
+ * <p>
+ * DO NOT nest an IFocusArea inside another IFocusArea because it will result in undefined
+ * navigation behavior.
+ */
+public interface IFocusArea {
+
+    /** Gets the {@link FocusAreaHelper} of this IFocusArea. */
+    @NonNull
+    FocusAreaHelper getHelper();
+
+    /** Gets the default focus view in this IFocusArea. */
+    @Nullable
+    View getDefaultFocusView();
+
+    /** Sets the default focus view in this IFocusArea. */
+    void setDefaultFocus(@NonNull View defaultFocus);
+
+    /**
+     * Sets the padding (in pixels) of the IFocusArea highlight.
+     * <p>
+     * It doesn't affect other values, such as the paddings on its child views.
+     */
+    void setHighlightPadding(int left, int top, int right, int bottom);
+
+    /**
+     * Sets the offset (in pixels) of the IFocusArea's perceived bounds.
+     * <p>
+     * It only affects the perceived bounds for the purposes of finding the nudge target. It doesn't
+     * affect the IFocusArea's view bounds or highlight bounds. The offset should only be used when
+     * IFocusAreas are overlapping and nudge interaction is ambiguous.
+     */
+    void setBoundsOffset(int left, int top, int right, int bottom);
+
+    /** Sets whether wrap-around is enabled for this IFocusArea. */
+    void setWrapAround(boolean wrapAround);
+
+    /**
+     * Sets the nudge shortcut for the given {@code direction}. Removes the nudge shortcut if
+     * {@code view} is {@code null}.
+     */
+    void setNudgeShortcut(int direction, @Nullable View view);
+
+    /**
+     * Sets the nudge target IFocusArea for the given {@code direction}. Removes the
+     * existing target if {@code target} is {@code null}.
+     */
+    void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target);
+
+    /**
+     * Sets whether to focus on the default focus view when nudging to the IFocusArea, even if there
+     * was another view in the IFocusArea focused before.
+     */
+    void setDefaultFocusOverridesHistory(boolean override);
+}
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/NestedContainer.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/NestedContainer.java
new file mode 100644
index 0000000..b828d5b
--- /dev/null
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/NestedContainer.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import com.android.car.ui.utils.ViewUtils;
+
+/**
+ * A container for hierarchical rotary navigation.
+ * <p>
+ * Use a NestedContainer in your layout when you want to have the entire container act as a single
+ * focusable view until the user chooses to enter it. For example, a long list where each element
+ * has many secondary actions can be tedious to scroll through. Wrapping each element in a
+ * NestedContainer makes it faster to scroll. If your list is short or has few secondary actions, a
+ * NestedContainer is probably not the best choice because it requires the user to explicitly enter
+ * (via the center button) and exit (via the Back button).
+ */
+public class NestedContainer extends FrameLayout {
+    /** Whether the user is navigating within this container. */
+    private boolean mFocusDescendant;
+
+    /** Focus change listener to exit this container when focus isn't within it. */
+    private final ViewTreeObserver.OnGlobalFocusChangeListener mFocusChangeListener =
+            (oldFocus, newFocus) -> {
+                if (mFocusDescendant && (isFocused() || !hasFocus())) {
+                    mFocusDescendant = false;
+                    setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
+                }
+            };
+
+    public NestedContainer(Context context) {
+        super(context);
+        init();
+    }
+
+    public NestedContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public NestedContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public NestedContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
+        setFocusable(true);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        getViewTreeObserver().addOnGlobalFocusChangeListener(mFocusChangeListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mFocusDescendant = false;
+        getViewTreeObserver().removeOnGlobalFocusChangeListener(mFocusChangeListener);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (super.dispatchKeyEvent(event)) return true;
+
+        if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER && !mFocusDescendant) {
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+                if (ViewUtils.focusOnFirstRegularView(this)) {
+                    mFocusDescendant = true;
+                } else {
+                    // We failed to focus a descendant so abort our attempt to enter this container.
+                    setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
+                }
+            }
+            return true;
+        }
+
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mFocusDescendant) {
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
+                requestFocus();
+                mFocusDescendant = false;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @inheritDoc
+     * <p>
+     * When {@link #mFocusDescendant} is true, the search is restricted to descendants of this
+     * {@link NestedContainer}.
+     */
+    @Override
+    public View focusSearch(View focused, int direction) {
+        View result = super.focusSearch(focused, direction);
+        if (mFocusDescendant && !ViewUtils.isDescendant(result, this)) {
+            result = null;
+        }
+        return result;
+    }
+}
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/RotaryCache.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/RotaryCache.java
index 97de7c3..955b66e 100644
--- a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/RotaryCache.java
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/RotaryCache.java
@@ -28,7 +28,8 @@
 import java.util.HashMap;
 
 /**
- * Cache used by {@link FocusArea} to save focus history and nudge history of the rotary controller.
+ * Cache used by {@link FocusAreaHelper} to save focus history and nudge history of the rotary
+ * controller.
  */
 class RotaryCache {
     /** The cache is disabled. */
@@ -51,7 +52,7 @@
     @NonNull
     private final FocusCache mFocusCache;
 
-    /** Cache of FocusAreas that were nudged to. */
+    /** Cache of focus areas that were nudged to. */
     @NonNull
     private final FocusAreaCache mFocusAreaCache;
 
@@ -118,15 +119,15 @@
         }
     }
 
-    /** A record of a FocusArea that was nudged to. */
+    /** A record of a focus area that was nudged to. */
     private static class FocusAreaHistory {
-        /** The FocusArea that was nudged to. */
+        /** The focus area that was nudged to. */
         @NonNull
-        final FocusArea mFocusArea;
+        final IFocusArea mFocusArea;
         /** The {@link SystemClock#uptimeMillis} when this history was recorded. */
         final long mTimestamp;
 
-        FocusAreaHistory(@NonNull FocusArea focusArea, long timestamp) {
+        FocusAreaHistory(@NonNull IFocusArea focusArea, long timestamp) {
             mFocusArea = focusArea;
             mTimestamp = timestamp;
         }
@@ -150,14 +151,14 @@
             }
         }
 
-        void put(int direction, @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
+        void put(int direction, @NonNull IFocusArea targetFocusArea, long elapsedRealtime) {
             if (mCacheType == CACHE_TYPE_DISABLED) {
                 return;
             }
             put(direction, new FocusAreaHistory(targetFocusArea, elapsedRealtime));
         }
 
-        FocusArea get(int direction, long elapsedRealtime) {
+        IFocusArea get(int direction, long elapsedRealtime) {
             FocusAreaHistory history = get(direction);
             return isValidHistory(history, elapsedRealtime) ? history.mFocusArea : null;
         }
@@ -187,7 +188,7 @@
     }
 
     /**
-     * Searches the cache to find the last focused view of the FocusArea. Returns the view, or null
+     * Searches the cache to find the last focused view of the focus area. Returns the view, or null
      * if there is nothing in the cache, the cache is stale.
      */
     @Nullable
@@ -201,20 +202,20 @@
     }
 
     /**
-     * Searches the cache to find the target FocusArea for a nudge in a given {@code direction}.
-     * Returns the target FocusArea, or null if there is nothing in the cache, the cache is stale.
+     * Searches the cache to find the target focus area for a nudge in a given {@code direction}.
+     * Returns the target focus area, or null if there is nothing in the cache, the cache is stale.
      */
     @Nullable
-    FocusArea getCachedFocusArea(int direction, long elapsedRealtime) {
+    IFocusArea getCachedFocusArea(int direction, long elapsedRealtime) {
         return mFocusAreaCache.get(direction, elapsedRealtime);
     }
 
-    /** Saves the FocusArea nudge history. */
-    void saveFocusArea(int direction, @NonNull FocusArea targetFocusArea, long elapsedRealtime) {
+    /** Saves the focus area nudge history. */
+    void saveFocusArea(int direction, @NonNull IFocusArea targetFocusArea, long elapsedRealtime) {
         mFocusAreaCache.put(direction, targetFocusArea, elapsedRealtime);
     }
 
-    /** Clears the FocusArea nudge history cache. */
+    /** Clears the focus area nudge history cache. */
     void clearFocusAreaHistory() {
         mFocusAreaCache.clear();
     }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
similarity index 100%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
rename to car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/DirectManipulationHelper.java
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
index e6c62c9..6ed71aa 100644
--- a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/RotaryConstants.java
@@ -57,26 +57,62 @@
 
     /**
      * Content description indicating that the view is a focus delegating container. When
-     * restoring focus, FocusParkingView and FocusArea will skip non-focusable views unless it's
+     * restoring focus, FocusParkingView and focus area will skip non-focusable views unless it's
      * a focus delegating container. The focus delegating container can delegate focus to one of
      * its descendants.
      */
     public static final String ROTARY_FOCUS_DELEGATING_CONTAINER =
             "com.android.car.ui.utils.FOCUS_DELEGATING_CONTAINER";
 
-    /** The key to store the offset of the FocusArea's left bound in the node's extras. */
+    /**
+     * The key to store the offset of the view's left bound for nudge in the node's extras.
+     * <p>
+     * Usually this value is positive in order to shrink the bounds. But a negative value can also
+     * be used to extend the bounds if needed.
+     */
+    public static final String LEFT_BOUND_OFFSET_FOR_NUDGE =
+            "com.android.car.ui.utils.LEFT_BOUND_OFFSET_FOR_NUDGE";
+
+    /**
+     * The key to store the offset of the view's right bound for nudge in the node's extras.
+     * <p>
+     * Usually this value is positive in order to shrink the bounds. But a negative value can also
+     * be used to extend the bounds if needed.
+     */
+    public static final String RIGHT_BOUND_OFFSET_FOR_NUDGE =
+            "com.android.car.ui.utils.RIGHT_BOUND_OFFSET_FOR_NUDGE";
+
+    /**
+     * The key to store the offset of the view's top bound for nudge in the node's extras.
+     * <p>
+     * Usually this value is positive in order to shrink the bounds. But a negative value can also
+     * be used to extend the bounds if needed.
+     */
+    public static final String TOP_BOUND_OFFSET_FOR_NUDGE =
+            "com.android.car.ui.utils.TOP_BOUND_OFFSET_FOR_NUDGE";
+
+    /**
+     * The key to store the offset of the view's bottom bound for nudge in the node's extras.
+     * <p>
+     * Usually this value is positive in order to shrink the bounds. But a negative value can also
+     * be used to extend the bounds if needed.
+     */
+    public static final String BOTTOM_BOUND_OFFSET_FOR_NUDGE =
+            "com.android.car.ui.utils.BOTTOM_BOUND_OFFSET_FOR_NUDGE";
+
+    /** The key to store the offset of the focus area's left bound in the node's extras. */
     public static final String FOCUS_AREA_LEFT_BOUND_OFFSET =
             "com.android.car.ui.utils.FOCUS_AREA_LEFT_BOUND_OFFSET";
 
-    /** The key to store the offset of the FocusArea's right bound in the node's extras. */
+    /** The key to store the offset of the focus area's right bound in the node's extras. */
     public static final String FOCUS_AREA_RIGHT_BOUND_OFFSET =
             "com.android.car.ui.utils.FOCUS_AREA_RIGHT_BOUND_OFFSET";
 
-    /** The key to store the offset of the FocusArea's top bound in the node's extras. */
+    /** The key to store the offset of the focus area's top bound in the node's extras. */
     public static final String FOCUS_AREA_TOP_BOUND_OFFSET =
             "com.android.car.ui.utils.FOCUS_AREA_TOP_BOUND_OFFSET";
 
-    /** The key to store the offset of the FocusArea's bottom bound in the node's extras. */
+    /** The key to store the offset of the focus area's bottom bound in the node's extras. */
     public static final String FOCUS_AREA_BOTTOM_BOUND_OFFSET =
             "com.android.car.ui.utils.FOCUS_AREA_BOTTOM_BOUND_OFFSET";
 
@@ -84,9 +120,12 @@
     public static final String NUDGE_DIRECTION =
             "com.android.car.ui.utils.NUDGE_DIRECTION";
 
+    /** The accessibility class name of {@link com.android.car.ui.IFocusArea} implementations. */
+    public static final String I_FOCUS_AREA_CLASS_NAME = "com.android.car.ui.FocusArea";
+
     /**
-     * Action performed on a FocusArea to move focus to the nudge shortcut within the same
-     * FocusArea.
+     * Action performed on a focus area to move focus to the nudge shortcut within the same
+     * focus area.
      * <p>
      * This action and the actions below only use the most significant 8 bits to avoid
      * conflicting with legacy standard actions (which don't use the most significant 8 bits),
@@ -95,7 +134,7 @@
      */
     public static final int ACTION_NUDGE_SHORTCUT = 0x01000000;
 
-    /** Action performed on a FocusArea to move focus to another FocusArea. */
+    /** Action performed on a focus area to move focus to another focus area. */
     public static final int ACTION_NUDGE_TO_ANOTHER_FOCUS_AREA = 0x02000000;
 
     /** Action performed on a FocusParkingView to restore the focus in the window. */
@@ -104,6 +143,9 @@
     /** Action performed on a FocusParkingView to hide the IME. */
     public static final int ACTION_HIDE_IME = 0x08000000;
 
+    /** Action performed on a FocusParkingView to dismiss a popup window. */
+    public static final int ACTION_DISMISS_POPUP_WINDOW = 0x10000000;
+
     /** Prevent instantiation. */
     private RotaryConstants() {
     }
diff --git a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
index fb8b4ab..f5abd44 100644
--- a/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
+++ b/car-ui-lib/car-rotary-lib/src/main/java/com/android/car/ui/utils/ViewUtils.java
@@ -23,35 +23,40 @@
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
 
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.ViewTreeObserver;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.car.ui.FocusArea;
 import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.IFocusArea;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.function.Predicate;
 
-/**
- * Utility class used by {@link com.android.car.ui.FocusArea} and {@link
- * com.android.car.ui.FocusParkingView}.
- *
- * @hide
- */
+/** Utility class for helpful methods related to {@link View} objects. */
+@SuppressWarnings("AndroidJdkLibsChecker")
 public final class ViewUtils {
 
+    private static final String TAG = "ViewUtils";
+
     /**
      * How many milliseconds to wait before trying to restore the focus inside the LazyLayoutView
      * the second time.
      */
-    private static final int RESTORE_FOCUS_RETRY_DELAY_MS = 3000;
+    @VisibleForTesting
+    static final int RESTORE_FOCUS_RETRY_DELAY_MS = 3000;
 
     /**
      * No view is focused, the focused view is not shown, or the focused view is a FocusParkingView.
@@ -70,27 +75,31 @@
     @VisibleForTesting
     static final int REGULAR_FOCUS = 3;
 
+    /** The selected view is focused. */
+    @VisibleForTesting
+    static final int SELECTED_FOCUS = 4;
+
     /**
      * An implicit default focus view (i.e., the selected item or the first focusable item in a
      * scrollable container) is focused.
      */
     @VisibleForTesting
-    static final int IMPLICIT_DEFAULT_FOCUS = 4;
+    static final int IMPLICIT_DEFAULT_FOCUS = 5;
 
     /** The {@code app:defaultFocus} view is focused. */
     @VisibleForTesting
-    static final int DEFAULT_FOCUS = 5;
+    static final int DEFAULT_FOCUS = 6;
 
     /** The {@code android:focusedByDefault} view is focused. */
     @VisibleForTesting
-    static final int FOCUSED_BY_DEFAULT = 6;
+    static final int FOCUSED_BY_DEFAULT = 7;
 
     /**
      * Focus level of a view. When adjusting the focus, the view with the highest focus level will
      * be focused.
      */
     @IntDef(flag = true, value = {NO_FOCUS, SCROLLABLE_CONTAINER_FOCUS, REGULAR_FOCUS,
-            IMPLICIT_DEFAULT_FOCUS, DEFAULT_FOCUS, FOCUSED_BY_DEFAULT})
+            SELECTED_FOCUS, IMPLICIT_DEFAULT_FOCUS, DEFAULT_FOCUS, FOCUSED_BY_DEFAULT})
     @Retention(RetentionPolicy.SOURCE)
     private @interface FocusLevel {
     }
@@ -100,17 +109,6 @@
     }
 
     /**
-     * This is a functional interface and can therefore be used as the assignment target for a
-     * lambda expression or method reference.
-     *
-     * @param <T> the type of the input to the predicate
-     */
-    private interface Predicate<T> {
-        /** Evaluates this predicate on the given argument. */
-        boolean test(@NonNull T t);
-    }
-
-    /**
      * An interface used to restore focus inside a view when its layout is completed.
      * <p>
      * The view that needs to restore focus lazily should implement this interface.
@@ -118,7 +116,7 @@
     public interface LazyLayoutView {
 
         /**
-         * Returnes whether the view's layout is completed and ready to restore focus inside it.
+         * Returns whether the view's layout is completed and ready to restore focus inside it.
          */
         boolean isLayoutCompleted();
 
@@ -133,6 +131,42 @@
         void removeOnLayoutCompleteListener(@Nullable Runnable runnable);
     }
 
+    /** Returns whether the {@code view} is in multi-window mode. */
+    public static boolean isInMultiWindowMode(@NonNull View view) {
+        Context context = view.getContext();
+        // Find the Activity context in case the view was inflated with Hilt dependency injector.
+        Activity activity = findActivity(context);
+        return activity != null && activity.isInMultiWindowMode();
+    }
+
+    /** Returns the Activity of the given {@code context}. */
+    @Nullable
+    public static Activity findActivity(@Nullable Context context) {
+        while (context instanceof ContextWrapper
+                && !(context instanceof Activity)) {
+            context = ((ContextWrapper) context).getBaseContext();
+        }
+        if (context instanceof Activity) {
+            return (Activity) context;
+        }
+        return null;
+    }
+
+    /** Returns whether the {@code descendant} view is a descendant of the {@code view}. */
+    public static boolean isDescendant(@Nullable View descendant, @Nullable View view) {
+        if (descendant == null || view == null) {
+            return false;
+        }
+        ViewParent parent = descendant.getParent();
+        while (parent != null) {
+            if (parent == view) {
+                return true;
+            }
+            parent = parent.getParent();
+        }
+        return false;
+    }
+
     /**
      * Hides the focus by searching the view tree for the {@link FocusParkingView}
      * and focusing on it.
@@ -142,9 +176,7 @@
      *         or if it was already focused
      */
     public static boolean hideFocus(@NonNull View root) {
-        FocusParkingView fpv = (FocusParkingView) depthFirstSearch(root,
-                /* targetPredicate= */ v -> v instanceof FocusParkingView,
-                /* skipPredicate= */ null);
+        FocusParkingView fpv = findFocusParkingView(root);
         if (fpv == null) {
             return false;
         }
@@ -154,13 +186,24 @@
         return fpv.performAccessibilityAction(ACTION_FOCUS, /* arguments= */ null);
     }
 
-    /** Gets the ancestor FocusArea of the {@code view}, if any. Returns null if not found. */
+    /**
+     * Returns the first {@link FocusParkingView} of the view tree, if any. Returns null if not
+     * found.
+     */
+    @VisibleForTesting
+    public static FocusParkingView findFocusParkingView(@NonNull View root) {
+        return (FocusParkingView) depthFirstSearch(root,
+                /* targetPredicate= */ v -> v instanceof FocusParkingView,
+                /* skipPredicate= */ null);
+    }
+
+    /** Gets the ancestor IFocusArea of the {@code view}, if any. Returns null if not found. */
     @Nullable
-    public static FocusArea getAncestorFocusArea(@NonNull View view) {
+    public static IFocusArea getAncestorFocusArea(@NonNull View view) {
         ViewParent parent = view.getParent();
         while (parent != null) {
-            if (parent instanceof FocusArea) {
-                return (FocusArea) parent;
+            if (parent instanceof IFocusArea) {
+                return (IFocusArea) parent;
             }
             parent = parent.getParent();
         }
@@ -177,9 +220,9 @@
             return null;
         }
         ViewParent parent = view.getParent();
-        // A scrollable container can't contain a FocusArea, so let's return earlier if we found
-        // a FocusArea.
-        while (parent != null && parent instanceof ViewGroup && !(parent instanceof FocusArea)) {
+        // A scrollable container can't contain an IFocusArea, so let's return earlier if we found
+        // an IFocusArea.
+        while (parent != null && parent instanceof ViewGroup && !(parent instanceof IFocusArea)) {
             ViewGroup viewGroup = (ViewGroup) parent;
             if (isScrollableContainer(viewGroup)) {
                 return viewGroup;
@@ -274,7 +317,7 @@
             @Nullable View cachedFocusedView,
             boolean defaultFocusOverridesHistory) {
         return adjustFocus(root, currentLevel, cachedFocusedView, defaultFocusOverridesHistory,
-                /* delayed= */true);
+                /* delayed= */ true);
     }
 
     /**
@@ -305,9 +348,12 @@
         if (currentLevel < IMPLICIT_DEFAULT_FOCUS && focusOnImplicitDefaultFocusView(root)) {
             return true;
         }
+        if (currentLevel < SELECTED_FOCUS && focusOnSelectedView(root)) {
+            return true;
+        }
 
         // When delayed is true, if there is a LazyLayoutView but it failed to adjust focus
-        // inside it because it is not loaded yet or it's loaded but has no descendnats, request to
+        // inside it because it hasn't loaded yet or it's loaded but has no descendants, request to
         // restore focus inside it later, and return false for now.
         if (delayed && currentLevel < IMPLICIT_DEFAULT_FOCUS) {
             LazyLayoutView lazyLayoutView = findLazyLayoutView(root);
@@ -336,7 +382,7 @@
     /**
      * If the {code lazyLayoutView} has a focusable descendant and no visible view is focused,
      * focuses on the descendant. Otherwise tries again when the {code lazyLayoutView} completes
-     * layout or after a timeout, whichever comes first.
+     * layout, shows up on the screen, or after a timeout, whichever comes first.
      */
     public static void initFocus(@NonNull LazyLayoutView lazyLayoutView) {
         if (initFocusImmediately(lazyLayoutView)) {
@@ -351,22 +397,88 @@
         }
         View lazyView = (View) lazyLayoutView;
         Runnable[] onLayoutCompleteListener = new Runnable[1];
-        Runnable delayedTask = () -> {
-            lazyLayoutView.removeOnLayoutCompleteListener(onLayoutCompleteListener[0]);
-            initFocusImmediately(lazyLayoutView);
-        };
-        onLayoutCompleteListener[0] = () -> {
-            if (initFocusImmediately(lazyLayoutView)) {
-                // Remove the delayedTask only when onLayoutCompleteListener has initialized the
-                // focus succefully, because the delayedTask needs to kick in when it fails, such
-                // as the lazyLayoutView is still loading after a timeout, or it's loaded but has
-                // no descendants to take focus.
-                lazyView.removeCallbacks(delayedTask);
-                lazyLayoutView.removeOnLayoutCompleteListener(onLayoutCompleteListener[0]);
+        Runnable[] delayedTask = new Runnable[1];
+        ViewTreeObserver.OnGlobalLayoutListener[] onGlobalLayoutListener =
+                new ViewTreeObserver.OnGlobalLayoutListener[1];
+
+        // If the lazyLayoutView has not completed layout yet, try to restore focus inside it once
+        // it's completed.
+        if (!lazyLayoutView.isLayoutCompleted()) {
+            Log.v(TAG, "The lazyLayoutView has not completed layout: " + lazyLayoutView);
+            onLayoutCompleteListener[0] = () -> {
+                Log.v(TAG, "The lazyLayoutView completed layout: "
+                        + lazyLayoutView);
+                if (initFocusImmediately(lazyLayoutView)) {
+                    Log.v(TAG, "Focus restored after lazyLayoutView completed layout");
+                    // Remove the other tasks only when onLayoutCompleteListener has initialized the
+                    // focus successfully, because the other tasks need to kick in when it fails,
+                    // such as when it has completed layout but has no descendants to take focus,
+                    // or it's not shown (e.g., its ancestor is invisible). In the former case,
+                    // the delayedTask needs to run after a timeout, while in the latter case the
+                    // onGlobalLayoutListener needs to run when it shows up on the screen.
+                    removeCallbacks(lazyLayoutView, onGlobalLayoutListener,
+                            onLayoutCompleteListener, delayedTask);
+                }
+            };
+            lazyLayoutView.addOnLayoutCompleteListener(onLayoutCompleteListener[0]);
+        }
+
+        // If the lazyLayoutView is not shown yet, try to restore focus inside it once it's shown.
+        if (!lazyView.isShown()) {
+            Log.d(TAG, "The lazyLayoutView is not shown: " + lazyLayoutView);
+            onGlobalLayoutListener[0] = () -> {
+                Log.d(TAG, "onGlobalLayoutListener is called");
+                if (lazyView.isShown()) {
+                    Log.d(TAG, "The lazyLayoutView is shown");
+                    if (initFocusImmediately(lazyLayoutView)) {
+                        Log.v(TAG, "Focus restored after showing lazyLayoutView");
+                        removeCallbacks(lazyLayoutView, onGlobalLayoutListener,
+                                onLayoutCompleteListener, delayedTask);
+                    }
+                }
+            };
+            lazyView.getViewTreeObserver()
+                    .addOnGlobalLayoutListener(onGlobalLayoutListener[0]);
+        }
+
+        // Run a delayed task as fallback.
+        delayedTask[0] = () -> {
+            Log.d(TAG, "Starting delayedTask");
+            removeCallbacks(lazyLayoutView, onGlobalLayoutListener,
+                    onLayoutCompleteListener, delayedTask);
+            if (!hasVisibleFocusInRoot(lazyView)) {
+                // Make one last attempt to restore focus inside the lazyLayoutView. For example,
+                // in ViewUtilsTest.testInitFocus_inLazyLayoutView5(), when lazyLayoutView's parent
+                // becomes visible, onGlobalLayoutListener won't be triggered, so it won't try to
+                // restore focus there.
+                if (lazyLayoutView.isLayoutCompleted() && lazyView.isShown()) {
+                    Log.d(TAG, "Last attempt to restore focus inside the lazyLayoutView");
+                    if (initFocusImmediately(lazyLayoutView)) {
+                        Log.d(TAG, "Restored focus inside the lazyLayoutView");
+                        return;
+                    }
+                }
+                // Search the view tree and find the view to focus when it failed to restore focus
+                // inside the lazyLayoutView.
+                adjustFocus(lazyView.getRootView(), NO_FOCUS, /* cachedFocusedView= */ null,
+                        /* defaultFocusOverridesHistory= */ false, /* delayed= */ false);
             }
         };
-        lazyLayoutView.addOnLayoutCompleteListener(onLayoutCompleteListener[0]);
-        lazyView.postDelayed(delayedTask, RESTORE_FOCUS_RETRY_DELAY_MS);
+        lazyView.postDelayed(delayedTask[0], RESTORE_FOCUS_RETRY_DELAY_MS);
+    }
+
+    private static void removeCallbacks(@NonNull LazyLayoutView lazyLayoutView,
+            ViewTreeObserver.OnGlobalLayoutListener[] onGlobalLayoutListener,
+            Runnable[] onLayoutCompleteListener,
+            Runnable[] delayedTask) {
+        lazyLayoutView.removeOnLayoutCompleteListener(onLayoutCompleteListener[0]);
+        if (!(lazyLayoutView instanceof View)) {
+            return;
+        }
+        View lazyView = (View) lazyLayoutView;
+        lazyView.removeCallbacks(delayedTask[0]);
+        lazyView.getViewTreeObserver()
+                .removeOnGlobalLayoutListener(onGlobalLayoutListener[0]);
     }
 
     private static boolean initFocusImmediately(@NonNull LazyLayoutView lazyLayoutView) {
@@ -374,16 +486,16 @@
             return false;
         }
         View lazyView = (View) lazyLayoutView;
-        View focusedView = lazyView.getRootView().findFocus();
-        // If the currently focused view won't draw, it's not a valid focus.
-        View visibleFocusedView =
-                focusedView != null && !focusedView.willNotDraw() ? focusedView : null;
-        // If there is a visible view focused, just return true.
-        if (visibleFocusedView != null && !(visibleFocusedView instanceof FocusParkingView)) {
+        // If there is a visible view focused in the view tree, just return true.
+        if (hasVisibleFocusInRoot(lazyView)) {
             return true;
         }
+        return ViewUtils.adjustFocusImmediately(lazyView, /* currentFocus= */ null);
+    }
 
-        return ViewUtils.adjustFocusImmediately(lazyView, visibleFocusedView);
+    private static boolean hasVisibleFocusInRoot(@NonNull View view) {
+        View focus = view.getRootView().findFocus();
+        return focus != null && !(focus instanceof FocusParkingView);
     }
 
     @VisibleForTesting
@@ -401,6 +513,9 @@
         if (isImplicitDefaultFocusView(view)) {
             return IMPLICIT_DEFAULT_FOCUS;
         }
+        if (view.isSelected()) {
+            return SELECTED_FOCUS;
+        }
         if (isScrollableContainer(view)) {
             return SCROLLABLE_CONTAINER_FOCUS;
         }
@@ -409,7 +524,7 @@
 
     /** Returns whether the {@code view} is a {@code app:defaultFocus} view. */
     private static boolean isDefaultFocus(@NonNull View view) {
-        FocusArea parent = getAncestorFocusArea(view);
+        IFocusArea parent = getAncestorFocusArea(view);
         return parent != null && view == parent.getDefaultFocusView();
     }
 
@@ -455,9 +570,10 @@
     }
 
     /**
-     * Focuses on the first {@code app:defaultFocus} view in the view tree, if any.
+     * Searches the {@code root}'s descendants for the first {@code app:defaultFocus} view and
+     * focuses on it, if any.
      *
-     * @param root the root of the view tree
+     * @param root the root view to search from
      * @return whether succeeded
      */
     private static boolean focusOnDefaultFocusView(@NonNull View root) {
@@ -466,9 +582,10 @@
     }
 
     /**
-     * Focuses on the first {@code android:focusedByDefault} view in the view tree, if any.
+     * Searches the {@code root}'s descendants for the first {@code android:focusedByDefault} view
+     * and focuses on it if any.
      *
-     * @param root the root of the view tree
+     * @param root the root view to search from
      * @return whether succeeded
      */
     private static boolean focusOnFocusedByDefaultView(@NonNull View root) {
@@ -477,9 +594,10 @@
     }
 
     /**
-     * Focuses on the first implicit default focus view in the view tree, if any.
+     * Searches the {@code root}'s descendants for the first implicit default focus view and focuses
+     * on it, if any.
      *
-     * @param root the root of the view tree
+     * @param root the root view to search from
      * @return whether succeeded
      */
     private static boolean focusOnImplicitDefaultFocusView(@NonNull View root) {
@@ -488,23 +606,39 @@
     }
 
     /**
-     * Tries to focus on the first focusable view in the view tree in depth first order, excluding
-     * the FocusParkingView and scrollable containers. If focusing on the first such view fails,
-     * keeps trying other views in depth first order until succeeds or there are no more such views.
+     * Searches the {@code root}'s descendants for the first selected view and focuses on it, if
+     * any.
      *
-     * @param root the root of the view tree
+     * @param root the root view to search from
      * @return whether succeeded
      */
-    private static boolean focusOnFirstRegularView(@NonNull View root) {
+    private static boolean focusOnSelectedView(@NonNull View root) {
+        View selectedView = findFirstSelectedFocusableDescendant(root);
+        return requestFocus(selectedView);
+    }
+
+    /**
+     * Searches the {@code root}'s descendants for the focusable view in depth first order
+     * (excluding the FocusParkingView and scrollable containers), and tries to focus on it.
+     * If focusing on the first such view fails, keeps trying other views in depth first order
+     * until succeeds or there are no more such views.
+     *
+     * @param root the root view to search from
+     * @return whether succeeded
+     */
+    public static boolean focusOnFirstRegularView(@NonNull View root) {
         View focusedView = ViewUtils.depthFirstSearch(root,
                 /* targetPredicate= */
-                v -> !isScrollableContainer(v) && canTakeFocus(v) && requestFocus(v),
+                v -> v != root && !isScrollableContainer(v) && canTakeFocus(v) && requestFocus(v),
                 /* skipPredicate= */ v -> !v.isShown());
         return focusedView != null;
     }
 
     /**
      * Focuses on the first scrollable container in the view tree, if any.
+     *<p>
+     * Unlike other similar methods, don't skip the {@code root} because some callers may pass
+     * a scrollable container as parameter.
      *
      * @param root the root of the view tree
      * @return whether succeeded
@@ -525,8 +659,8 @@
         if (!view.isShown()) {
             return null;
         }
-        if (view instanceof FocusArea) {
-            FocusArea focusArea = (FocusArea) view;
+        if (view instanceof IFocusArea) {
+            IFocusArea focusArea = (IFocusArea) view;
             View defaultFocus = focusArea.getDefaultFocusView();
             if (defaultFocus != null && canTakeFocus(defaultFocus)) {
                 return defaultFocus;
@@ -545,19 +679,19 @@
     }
 
     /**
-     * Searches the {@code view} and its descendants in depth first order, and returns the first
+     * Searches the {@code view}'s descendants in depth first order, and returns the first
      * {@code android:focusedByDefault} view that can take focus. Returns null if not found.
      */
     @VisibleForTesting
     @Nullable
     static View findFocusedByDefaultView(@NonNull View view) {
         return depthFirstSearch(view,
-                /* targetPredicate= */ v -> v.isFocusedByDefault() && canTakeFocus(v),
+                /* targetPredicate= */ v -> v != view && v.isFocusedByDefault() && canTakeFocus(v),
                 /* skipPredicate= */ v -> !v.isShown());
     }
 
     /**
-     * Searches the {@code view} and its descendants in depth first order, and returns the first
+     * Searches the {@code view}'s descendants in depth first order, and returns the first
      * implicit default focus view, i.e., the selected item or the first focusable item in the
      * first rotary container. Returns null if not found.
      */
@@ -602,18 +736,34 @@
 
     /**
      * Searches the {@code view} and its descendants in depth first order, and returns the first
-     * rotary container shown on the screen. Returns null if not found.
+     * rotary container shown on the screen. If the rotary containers are LazyLayoutViews, returns
+     * the first layout completed one. Returns null if not found.
+     * <p>
+     * Unlike other similar methods, don't skip the {@code root} because some callers may pass
+     * a rotary container as parameter.
      */
     @Nullable
     private static View findRotaryContainer(@NonNull View view) {
         return depthFirstSearch(view,
-                /* targetPredicate= */ v -> isRotaryContainer(v),
-                /* skipPredicate= */ v -> !v.isShown());
+                /* targetPredicate= */ ViewUtils::isRotaryContainer,
+                /* skipPredicate= */ v -> {
+                    if (!v.isShown()) {
+                        return true;
+                    }
+                    if (v instanceof LazyLayoutView) {
+                        LazyLayoutView lazyLayoutView = (LazyLayoutView) v;
+                        return !lazyLayoutView.isLayoutCompleted();
+                    }
+                    return false;
+                });
     }
 
     /**
      * Searches the {@code view} and its descendants in depth first order, and returns the first
      * LazyLayoutView shown on the screen. Returns null if not found.
+     * <p>
+     * Unlike other similar methods, don't skip the {@code root} because some callers may pass
+     * a LazyLayoutView as parameter.
      */
     @Nullable
     private static LazyLayoutView findLazyLayoutView(@NonNull View view) {
diff --git a/car-ui-lib/car-rotary-lib/src/main/res/values/attrs.xml b/car-ui-lib/car-rotary-lib/src/main/res/values/attrs.xml
index 9aa880c..16ea2c7 100644
--- a/car-ui-lib/car-rotary-lib/src/main/res/values/attrs.xml
+++ b/car-ui-lib/car-rotary-lib/src/main/res/values/attrs.xml
@@ -14,21 +14,22 @@
  limitations under the License.
 -->
 <resources>
-  <!-- Attributes for FocusArea. -->
-  <declare-styleable name="FocusArea">
+  <!-- Attributes for IFocusArea. -->
+  <declare-styleable name="IFocusArea">
     <!-- The ID of the default focus view. The view will be prioritized when searching for a
          focus target.
-         (1) When the user nudges the rotary controller, it will search for a target FocusArea,
-             then search for a target view within the target FocusArea, and focus on the target
+         (1) When the user nudges the rotary controller, it will search for a target IFocusArea,
+             then search for a target view within the target IFocusArea, and focus on the target
              view. The target view is chosen in the following order:
                1. the "android:focusedByDefault" view, if any
                2. the "app:defaultFocus" view, if any
                3. the selected item in a scrollable container, if any
                4. the first focusable item in a scrollable container, if any
-               5. previously focused view, if any and the cache is not stale
-               6. the first focusable view, if any
-             Note that 5 will be prioritized over 1, 2, 3, and 4 when
-             "app:defaultFocusOverridesHistory" is true.
+               5. the first selected view, if any
+               6. previously focused view, if any and the cache is not stale
+               7. the first focusable view, if any
+             Note that 6 will be prioritized over 1, 2, 3, 4, and 5 when
+             "app:defaultFocusOverridesHistory" is false.
          (2) When it needs to initialize the focus (such as when a window is opened), it will
              search for a view in the window and focus on it. The view is chosen in the
              following order:
@@ -36,56 +37,57 @@
                2. the first "app:defaultFocus" view, if any
                3. the selected item in a scrollable container, if any
                4. the first focusable item in a scrollable container, if any
-               5. the first focusable view that is not a FocusParkingView, if any
-         If there is only one FocusArea that needs to set default focus, you can use either
+               5. the first selected view, if any
+               6. the first focusable view that is not a FocusParkingView, if any
+         If there is only one IFocusArea that needs to set default focus, you can use either
          "app:defaultFocus" or "android:focusedByDefault". If there are more than one, you
-         should use "android:focusedByDefault" in the primary FocusArea, and use
-         "app:defaultFocus" in other FocusAreas. -->
+         should use "android:focusedByDefault" in the primary IFocusArea, and use
+         "app:defaultFocus" in other IFocusAreas. -->
     <attr name="defaultFocus" format="reference"/>
 
-    <!-- Whether to focus on the default focus view when nudging to the FocusArea, even if there
-         was another view in the FocusArea focused before. -->
+    <!-- Whether to focus on the default focus view when nudging to the IFocusArea, even if there
+         was another view in the IFocusArea focused before. -->
     <attr name="defaultFocusOverridesHistory" format="boolean"/>
 
-    <!-- The paddings of FocusArea highlight. It does't impact the paddings on its child views,
+    <!-- The paddings of IFocusArea highlight. It doesn't impact the paddings on its child views,
          or vice versa. -->
-    <!-- The start padding of the FocusArea highlight. -->
+    <!-- The start padding of the IFocusArea highlight. -->
     <attr name="highlightPaddingStart" format="dimension"/>
-    <!-- The end padding of the FocusArea highlight. -->
+    <!-- The end padding of the IFocusArea highlight. -->
     <attr name="highlightPaddingEnd" format="dimension"/>
-    <!-- The top padding of the FocusArea highlight. -->
+    <!-- The top padding of the IFocusArea highlight. -->
     <attr name="highlightPaddingTop" format="dimension"/>
-    <!-- The bottom padding of the FocusArea highlight. -->
+    <!-- The bottom padding of the IFocusArea highlight. -->
     <attr name="highlightPaddingBottom" format="dimension"/>
-    <!-- The horizontal padding of the FocusArea highlight. It can be overridden by
+    <!-- The horizontal padding of the IFocusArea highlight. It can be overridden by
          highlightPaddingStart or highlightPaddingEnd. -->
     <attr name="highlightPaddingHorizontal" format="dimension"/>
-    <!-- The vertical padding of the FocusArea highlight.  It can be overridden by
+    <!-- The vertical padding of the IFocusArea highlight.  It can be overridden by
          highlightPaddingTop or highlightPaddingBottom. -->
     <attr name="highlightPaddingVertical" format="dimension"/>
 
-    <!-- The offset of the FocusArea's bounds. It only affects the perceived bounds for the
-         purposes of finding the nudge target. It doesn't affect the FocusArea's view bounds or
-         highlight bounds. The offset should only be used when FocusAreas are overlapping and
+    <!-- The offset of the IFocusArea's bounds. It only affects the perceived bounds for the
+         purposes of finding the nudge target. It doesn't affect the IFocusArea's view bounds or
+         highlight bounds. The offset should only be used when IFocusAreas are overlapping and
          nudge interaction is ambiguous. -->
-    <!-- The offset of the FocusArea's start bound. -->
+    <!-- The offset of the IFocusArea's start bound. -->
     <attr name="startBoundOffset" format="dimension"/>
-    <!-- The offset of the FocusArea's end bound. -->
+    <!-- The offset of the IFocusArea's end bound. -->
     <attr name="endBoundOffset" format="dimension"/>
-    <!-- The offset of the FocusArea's top bound. -->
+    <!-- The offset of the IFocusArea's top bound. -->
     <attr name="topBoundOffset" format="dimension"/>
-    <!-- The offset of the FocusArea's bottom bound. -->
+    <!-- The offset of the IFocusArea's bottom bound. -->
     <attr name="bottomBoundOffset" format="dimension"/>
-    <!-- The offset of the FocusArea's horizontal bounds. It can be overridden by
+    <!-- The offset of the IFocusArea's horizontal bounds. It can be overridden by
          startBoundOffset or endBoundOffset. -->
     <attr name="horizontalBoundOffset" format="dimension"/>
-    <!-- The offset of the FocusArea's vertical bounds. It can be overridden by topBoundOffset
+    <!-- The offset of the IFocusArea's vertical bounds. It can be overridden by topBoundOffset
          or bottomBoundOffset. -->
     <attr name="verticalBoundOffset" format="dimension"/>
 
     <!-- New attributes for nudge shortcuts. Usually nudge is used to navigate to another
-         FocusArea, but when a nudge shortcut is specified, it's used to navigate to the
-         given view within the same FocusArea. A nudge shortcut can be specified for each
+         IFocusArea, but when a nudge shortcut is specified, it's used to navigate to the
+         given view within the same IFocusArea. A nudge shortcut can be specified for each
          direction. -->
     <!-- The ID of the nudge left shortcut view. -->
     <attr name="nudgeLeftShortcut" format="reference"/>
@@ -97,8 +99,8 @@
     <attr name="nudgeDownShortcut" format="reference"/>
 
     <!-- Legacy attributes for nudge shortcut. Usually nudge is used to navigate to another
-         FocusArea, but when a nudge shortcut is specified, it's used to navigate to the given
-         view within the same FocusArea. If using these legacy attributes, the two must be
+         IFocusArea, but when a nudge shortcut is specified, it's used to navigate to the given
+         view within the same IFocusArea. If using these legacy attributes, the two must be
          specified together and the new attributes cannot be used. -->
     <!-- The ID of the nudge shortcut view. -->
     <attr name="nudgeShortcut" format="reference"/>
@@ -114,18 +116,18 @@
       <flag name="down" value="0x82" />
     </attr>
 
-    <!-- Attributes to specify the target FocusArea for a nudge. -->
-    <!-- The ID of the target FocusArea when nudging to the left. -->
+    <!-- Attributes to specify the target IFocusArea for a nudge. -->
+    <!-- The ID of the target IFocusArea when nudging to the left. -->
     <attr name="nudgeLeft" format="reference"/>
-    <!-- The ID of the target FocusArea when nudging to the right. -->
+    <!-- The ID of the target IFocusArea when nudging to the right. -->
     <attr name="nudgeRight" format="reference"/>
-    <!-- The ID of the target FocusArea when nudging up. -->
+    <!-- The ID of the target IFocusArea when nudging up. -->
     <attr name="nudgeUp" format="reference"/>
-    <!-- The ID of the target FocusArea when nudging down. -->
+    <!-- The ID of the target IFocusArea when nudging down. -->
     <attr name="nudgeDown" format="reference"/>
 
     <!-- Whether rotation wraps around. When true, rotation wraps around, staying within the
-         FocusArea, when it reaches the first or last focusable view in the FocusArea. When
+         IFocusArea, when it reaches the first or last focusable view in the IFocusArea. When
          false, rotation does nothing in this case. -->
     <attr name="wrapAround" format="boolean"/>
   </declare-styleable>
diff --git a/car-ui-lib/car-ui-androidx/Android.bp b/car-ui-lib/car-ui-androidx/Android.bp
deleted file mode 100644
index ca51962..0000000
--- a/car-ui-lib/car-ui-androidx/Android.bp
+++ /dev/null
@@ -1,604 +0,0 @@
-//
-// Copyright (C) 2020 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "car-ui-lib-androidx",
-    srcs: [],
-    resource_dirs: [],
-    sdk_version: "system_current",
-    optimize: {
-        enabled: false,
-    },
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-activity",
-        "car-ui-androidx-appcompat",
-        "car-ui-androidx-appcompat-resources",
-        "car-ui-androidx-asynclayoutinflater",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-constraintlayout",
-        "car-ui-androidx-constraintlayout-solver",
-        "car-ui-androidx-core",
-        "car-ui-androidx-core-common",
-        "car-ui-androidx-core-runtime",
-        "car-ui-androidx-customview",
-        "car-ui-androidx-drawerlayout",
-        "car-ui-androidx-fragment",
-        "car-ui-androidx-lifecycle-common",
-        "car-ui-androidx-lifecycle-livedata-core",
-        "car-ui-androidx-lifecycle-runtime",
-        "car-ui-androidx-lifecycle-viewmodel",
-        "car-ui-androidx-loader",
-        "car-ui-androidx-preference",
-        "car-ui-androidx-recyclerview",
-        "car-ui-androidx-savedstate",
-        "car-ui-androidx-vectordrawable",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-constraintlayout-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx-constraintlayout_constraintlayout-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-constraintlayout.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-constraintlayout",
-    aars: [":car-ui-androidx-constraintlayout-nodeps"],
-    static_libs: [
-        "car-ui-androidx-appcompat",
-        "car-ui-androidx-core",
-        "car-ui-androidx-constraintlayout-solver",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-constraintlayout-solver-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx-constraintlayout_constraintlayout-solver-nodeps{.jar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-constraintlayout-solver.jar"],
-}
-
-java_import {
-    name: "car-ui-androidx-constraintlayout-solver",
-    jars: [":car-ui-androidx-constraintlayout-solver-nodeps"],
-}
-
-genrule {
-    name: "car-ui-androidx-preference-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.preference_preference-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-preference.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-preference",
-    aars: [":car-ui-androidx-preference-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-appcompat",
-        "car-ui-androidx-core",
-        "car-ui-androidx-fragment",
-        "car-ui-androidx-recyclerview",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-recyclerview-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.recyclerview_recyclerview-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-recyclerview.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-recyclerview",
-    aars: [":car-ui-androidx-recyclerview-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-        "car-ui-androidx-customview",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-asynclayoutinflater-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.asynclayoutinflater_asynclayoutinflater-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-asynclayoutinflater.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-asynclayoutinflater",
-    aars: [":car-ui-androidx-asynclayoutinflater-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-core",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-appcompat-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.appcompat_appcompat-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-appcompat.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-appcompat",
-    aars: [":car-ui-androidx-appcompat-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-        "car-ui-androidx-cursoradapter",
-        "car-ui-androidx-activity",
-        "car-ui-androidx-fragment",
-        "car-ui-androidx-appcompat-resources",
-        "car-ui-androidx-drawerlayout",
-        "car-ui-androidx-savedstate",
-        "car-ui-androidx-lifecycle-runtime",
-        "car-ui-androidx-lifecycle-viewmodel",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-cursoradapter-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.cursoradapter_cursoradapter-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-cursoradapter.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-cursoradapter",
-    aars: [":car-ui-androidx-cursoradapter-nodeps"],
-    static_libs: [
-        "androidx.annotation_annotation",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-fragment-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.fragment_fragment-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-fragment.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-fragment",
-    aars: [":car-ui-androidx-fragment-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-        "car-ui-androidx-viewpager",
-        "car-ui-androidx-loader",
-        "car-ui-androidx-activity",
-        "car-ui-androidx-lifecycle-livedata-core",
-        "car-ui-androidx-lifecycle-viewmodel",
-        "car-ui-androidx-lifecycle-viewmodel-savedstate",
-        "car-ui-androidx-savedstate",
-        // "androidx.annotation_annotation-experimental",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-loader-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.loader_loader-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-loader.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-loader",
-    aars: [":car-ui-androidx-loader-nodeps"],
-    static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.collection_collection",
-        "androidx.lifecycle_lifecycle-viewmodel",
-        "androidx.core_core",
-        "androidx.lifecycle_lifecycle-livedata-core",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-viewpager-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.viewpager_viewpager-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-viewpager.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-viewpager",
-    aars: [":car-ui-androidx-viewpager-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-customview",
-        "car-ui-androidx-core",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-activity-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.activity_activity-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-activity.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-activity",
-    aars: [":car-ui-androidx-activity-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-        "car-ui-androidx-lifecycle-runtime",
-        "car-ui-androidx-lifecycle-viewmodel",
-        "car-ui-androidx-savedstate",
-        "car-ui-androidx-lifecycle-viewmodel-savedstate",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-lifecycle-viewmodel-savedstate-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.lifecycle_lifecycle-viewmodel-savedstate-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-lifecycle-viewmodel-savedstate.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-lifecycle-viewmodel-savedstate",
-    aars: [":car-ui-androidx-lifecycle-viewmodel-savedstate-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-savedstate",
-        "car-ui-androidx-lifecycle-livedata-core",
-        "car-ui-androidx-lifecycle-viewmodel",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-drawerlayout-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.drawerlayout_drawerlayout-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-drawerlayout.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-drawerlayout",
-    aars: [":car-ui-androidx-drawerlayout-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-core",
-        "car-ui-androidx-customview",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-customview-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.customview_customview-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-customview.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-customview",
-    aars: [":car-ui-androidx-customview-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-savedstate-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.savedstate_savedstate-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-savedstate.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-savedstate",
-    aars: [":car-ui-androidx-savedstate-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-core-common",
-        "car-ui-androidx-lifecycle-common",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-appcompat-resources-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.appcompat_appcompat-resources-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-appcompat-resources.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-appcompat-resources",
-    aars: [":car-ui-androidx-appcompat-resources-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-        "car-ui-androidx-vectordrawable",
-        // "car-ui-androidx-vectordrawable-animated",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-vectordrawable-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.vectordrawable_vectordrawable-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-vectordrawable.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-vectordrawable",
-    aars: [":car-ui-androidx-vectordrawable-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-core",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-core-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.core_core-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-core.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-core",
-    aars: [":car-ui-androidx-core-nodeps"],
-    static_libs: [
-        "car-ui-androidx-annotation",
-        "car-ui-androidx-collection",
-        "car-ui-androidx-lifecycle-runtime",
-        //"car-ui-androidx-versionedparcelable"
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-lifecycle-livedata-core-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.lifecycle_lifecycle-livedata-core-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-lifecycle-livedata-core.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-lifecycle-livedata-core",
-    aars: [":car-ui-androidx-lifecycle-livedata-core-nodeps"],
-    static_libs: [
-        "car-ui-androidx-lifecycle-common",
-        "car-ui-androidx-core-common",
-        "car-ui-androidx-core-runtime",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-lifecycle-viewmodel-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.lifecycle_lifecycle-viewmodel-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-lifecycle-viewmodel.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-lifecycle-viewmodel",
-    aars: [":car-ui-androidx-lifecycle-viewmodel-nodeps"],
-    sdk_version: "current",
-    static_libs: [
-        "car-ui-androidx-annotation",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-lifecycle-runtime-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.lifecycle_lifecycle-runtime-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-lifecycle-runtime.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-lifecycle-runtime",
-    aars: [":car-ui-androidx-lifecycle-runtime-nodeps"],
-    sdk_version: "current",
-    static_libs: [
-        "car-ui-androidx-lifecycle-common",
-        "car-ui-androidx-core-runtime",
-        "car-ui-androidx-core-common",
-        "car-ui-androidx-annotation",
-    ],
-}
-
-genrule {
-    name: "car-ui-androidx-lifecycle-common-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.lifecycle_lifecycle-common-nodeps{.jar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-lifecycle-common.jar"],
-}
-
-java_import {
-    name: "car-ui-androidx-lifecycle-common",
-    jars: [":car-ui-androidx-lifecycle-common-nodeps"],
-    sdk_version: "current",
-}
-
-genrule {
-    name: "car-ui-androidx-core-runtime-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.arch.core_core-runtime-nodeps{.aar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-core-runtime.aar"],
-}
-
-android_library_import {
-    name: "car-ui-androidx-core-runtime",
-    aars: [":car-ui-androidx-core-runtime-nodeps"],
-    sdk_version: "current",
-}
-
-genrule {
-    name: "car-ui-androidx-core-common-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.arch.core_core-common-nodeps{.jar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-core-common.aar"],
-}
-
-java_import {
-    name: "car-ui-androidx-core-common",
-    jars: [":car-ui-androidx-core-common-nodeps"],
-    sdk_version: "current",
-}
-
-java_genrule {
-    name: "car-ui-androidx-annotation-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.annotation_annotation-nodeps{.jar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-annotation.jar"],
-    host_supported: true,
-}
-
-java_import {
-    name: "car-ui-androidx-annotation",
-    jars: [":car-ui-androidx-annotation-nodeps"],
-    sdk_version: "current",
-    host_supported: true,
-}
-
-genrule {
-    name: "car-ui-androidx-collection-nodeps",
-    tools: ["jetifier"],
-    srcs: [
-        ":androidx.collection_collection-nodeps{.jar}",
-        "car-ui-jetifier-reverse.cfg",
-    ],
-    cmd: "$(location jetifier) -i $(in) -o $(out) -r -c $(location car-ui-jetifier-reverse.cfg)",
-    out: ["car-ui-androidx-collection.jar"],
-}
-
-java_import {
-    name: "car-ui-androidx-collection",
-    jars: [":car-ui-androidx-collection-nodeps"],
-    sdk_version: "current",
-}
diff --git a/car-ui-lib/car-ui-androidx/AndroidManifest.xml b/car-ui-lib/car-ui-androidx/AndroidManifest.xml
deleted file mode 100644
index 4b60871..0000000
--- a/car-ui-lib/car-ui-androidx/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2020 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.ui.androidx">
-  <uses-sdk
-      android:minSdkVersion="29"
-      android:targetSdkVersion="30" />
-    <application>
-    </application>
-</manifest>
diff --git a/car-ui-lib/car-ui-androidx/README.md b/car-ui-lib/car-ui-androidx/README.md
deleted file mode 100644
index f9e821f..0000000
--- a/car-ui-lib/car-ui-androidx/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Android Automotive 'Chassis' re-packaged androidx
-Please refer to [Android Automotive 'Chassiss' shared library](../sharedlibrary/README.md)
-
-All the applications that are using both shared library version of car-ui-lib and and AndroidX will end up having 2 copies of AndroidX in their class path. One copy from the shared library and one version from the application itself. At runtime, the Android class loader finds the AndroidX classes from the shared library first. This could lead to compatibility issues, for example when the two versions are not compatible. In order to avoid that we're repackaging AndroidX binaries from `androiodx.*` to `androidx.car.ui.*`. `car-ui-lib-androidx` build target will be used with shared library version of car-ui-lib.
-
-car-ui-jetifier-reverse.cfg file is a manually edited file to match the requirements. This file needs maintainance as AndroidX adds more classes to the packages that are currently used by car-ui and/or if any new AndroidX package is needed by car-ui that's not listed there.
diff --git a/car-ui-lib/car-ui-androidx/car-ui-jetifier-reverse.cfg b/car-ui-lib/car-ui-androidx/car-ui-jetifier-reverse.cfg
deleted file mode 100644
index 558ba50..0000000
--- a/car-ui-lib/car-ui-androidx/car-ui-jetifier-reverse.cfg
+++ /dev/null
@@ -1,2963 +0,0 @@
-# Copyright (C) 2018 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
-
-# DO NOT EDIT MANUALLY! This file was auto-generated using Jetifier preprocessor.
-# To make some changes in the configuration edit "default.config" and run
-# preprocessor/scripts/processDefaultConfig.sh script to update this file.
-
-{
-  "restrictToPackagePrefixes": [
-    "android/support/",
-    "android/arch/",
-    "android/databinding/",
-    "com/android/databinding/library/baseAdapters/"
-  ],
-  "reversedRestrictToPackagePrefixes": [
-    "androidx/",
-    "com/google/android/material/"
-  ],
-  "rules": [
-    {
-      "from": "(.*)BuildConfig",
-      "to": "ignoreInPreprocessorOnly"
-    },
-    {
-      "from": "(.*)/package-info",
-      "to": "ignoreInPreprocessorOnly"
-    },
-    {
-      "from": "android/support/exifinterface/test/R(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "android/support/test/((.*)/)?internal/(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "android/support/v4/os/ResultReceiver(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "(.*)Parcelizer",
-      "to": "{0}Parcelizer"
-    },
-    {
-      "from": "androidx/car/ui/core/R(.*)",
-      "to": "androidx/core/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/R(.*)",
-      "to": "androidx/recyclerview/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/appcompat/R(.*)",
-      "to": "androidx/appcompat/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/savedstate/R(.*)",
-      "to": "androidx/savedstate/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/vectordrawable/R(.*)",
-      "to": "androidx/vectordrawable/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/preference/R(.*)",
-      "to": "androidx/preference/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/loader/R(.*)",
-      "to": "androidx/loader/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/viewmodel/R(.*)",
-      "to": "androidx/lifecycle/viewmodel/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/runtime/R(.*)",
-      "to": "androidx/lifecycle/runtime/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/livedata/core/R(.*)",
-      "to": "androidx/lifecycle/livedata/core/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/R(.*)",
-      "to": "androidx/fragment/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/drawerlayout/R(.*)",
-      "to": "androidx/drawerlayout/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/customview/R(.*)",
-      "to": "androidx/customview/R{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/AdapterHelper(.*)",
-      "to": "androidx/recyclerview/widget/AdapterHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/ChildHelper(.*)",
-      "to": "androidx/recyclerview/widget/ChildHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/DefaultItemAnimator(.*)",
-      "to": "androidx/recyclerview/widget/DefaultItemAnimator{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/DividerItemDecoration(.*)",
-      "to": "androidx/recyclerview/widget/DividerItemDecoration{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/FastScroller(.*)",
-      "to": "androidx/recyclerview/widget/FastScroller{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/GapWorker(.*)",
-      "to": "androidx/recyclerview/widget/GapWorker{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/GridLayoutManager(.*)",
-      "to": "androidx/recyclerview/widget/GridLayoutManager{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/LayoutState(.*)",
-      "to": "androidx/recyclerview/widget/LayoutState{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/LinearLayoutManager(.*)",
-      "to": "androidx/recyclerview/widget/LinearLayoutManager{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/LinearSmoothScroller(.*)",
-      "to": "androidx/recyclerview/widget/LinearSmoothScroller{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/LinearSnapHelper(.*)",
-      "to": "androidx/recyclerview/widget/LinearSnapHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/OpReorderer(.*)",
-      "to": "androidx/recyclerview/widget/OpReorderer{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/OrientationHelper(.*)",
-      "to": "androidx/recyclerview/widget/OrientationHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/PagerSnapHelper(.*)",
-      "to": "androidx/recyclerview/widget/PagerSnapHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/PositionMap(.*)",
-      "to": "androidx/recyclerview/widget/PositionMap{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/RecyclerViewAccessibilityDelegate(.*)",
-      "to": "androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/RecyclerView(.*)",
-      "to": "androidx/recyclerview/widget/RecyclerView{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/ScrollbarHelper(.*)",
-      "to": "androidx/recyclerview/widget/ScrollbarHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/SimpleItemAnimator(.*)",
-      "to": "androidx/recyclerview/widget/SimpleItemAnimator{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/SnapHelper(.*)",
-      "to": "androidx/recyclerview/widget/SnapHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/StaggeredGridLayoutManager(.*)",
-      "to": "androidx/recyclerview/widget/StaggeredGridLayoutManager{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/ViewBoundsCheck(.*)",
-      "to": "androidx/recyclerview/widget/ViewBoundsCheck{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/ViewInfoStore(.*)",
-      "to": "androidx/recyclerview/widget/ViewInfoStore{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/(.*)",
-      "to": "androidx/recyclerview/widget/{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/helper/(.*)",
-      "to": "androidx/recyclerview/widget/{0}"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview/widget/util/(.*)",
-      "to": "androidx/recyclerview/widget/{0}"
-    },
-    {
-      "from": "androidx/car/ui/preference/(.*)",
-      "to": "androidx/preference/{0}"
-    },
-    {
-      "from": "androidx/car/ui/preference/internal/PreferenceImageView(.*)",
-      "to": "androidx/preference/internal/PreferenceImageView{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/content/res/GrowingArrayUtils(.*)",
-      "to": "androidx/core/content/res/GrowingArrayUtils{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/content/res/GrowingArrayUtils(.*)",
-      "to": "androidx/core/content/res/GrowingArrayUtils{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/content/res/ColorStateListInflaterCompat(.*)",
-      "to": "androidx/core/content/res/ColorStateListInflaterCompat{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/content/res/ColorStateListInflaterCompat(.*)",
-      "to": "androidx/core/content/res/ColorStateListInflaterCompat{0}"
-    },
-    {
-      "from": "androidx/car/ui/appcompat/(.*)",
-      "to": "androidx/appcompat/{0}"
-    },
-    {
-      "from": "androidx/car/ui/vectordrawable/graphics/drawable/(.*)",
-      "to": "androidx/vectordrawable/graphics/drawable/{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/view/animation/PathInterpolatorApi14(.*)",
-      "to": "androidx/core/view/animation/PathInterpolatorApi14{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/view/animation/PathInterpolatorCompat(.*)",
-      "to": "androidx/core/view/animation/PathInterpolatorCompat{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/app/ComponentActivity(.*)",
-      "to": "androidx/core/app/ComponentActivity{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/Fragment(.*)",
-      "to": "androidx/fragment/app/Fragment{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/BaseFragment(.*)",
-      "to": "androidx/fragment/app/BaseFragment{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/BackStack(.*)",
-      "to": "androidx/fragment/app/BackStack{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/DialogFragment(.*)",
-      "to": "androidx/fragment/app/DialogFragment{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/ListFragment(.*)",
-      "to": "androidx/fragment/app/ListFragment{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/OneShotPreDrawListener(.*)",
-      "to": "androidx/fragment/app/OneShotPreDrawListener{0}"
-    },
-    {
-      "from": "androidx/car/ui/fragment/app/SuperNotCalledException(.*)",
-      "to": "androidx/fragment/app/SuperNotCalledException{0}"
-    },
-    {
-      "from": "androidx/car/ui/app/LoaderManager(.*)",
-      "to": "androidx/loader/app/LoaderManager{0}"
-    },
-    {
-      "from": "androidx/car/ui/content/Loader(.*)",
-      "to": "androidx/loader/content/Loader{0}"
-    },
-    {
-      "from": "androidx/car/ui/content/CursorLoader(.*)",
-      "to": "androidx/loader/content/CursorLoader{0}"
-    },
-    {
-      "from": "androidx/car/ui/content/AsyncTaskLoader(.*)",
-      "to": "androidx/loader/content/AsyncTaskLoader{0}"
-    },
-    {
-      "from": "androidx/car/ui/content/ModernAsyncTask(.*)",
-      "to": "androidx/loader/content/ModernAsyncTask{0}"
-    },
-    {
-      "from": "androidx/car/ui/customview/view/AbsSavedState(.*)",
-      "to": "androidx/customview/view/AbsSavedState{0}"
-    },
-    {
-      "from": "androidx/car/ui/customview/widget/ExploreByTouchHelper(.*)",
-      "to": "androidx/customview/widget/ExploreByTouchHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/customview/widget/FocusStrategy(.*)",
-      "to": "androidx/customview/widget/FocusStrategy{0}"
-    },
-    {
-      "from": "androidx/car/ui/customview/widget/ViewDragHelper(.*)",
-      "to": "androidx/customview/widget/ViewDragHelper{0}"
-    },
-    {
-      "from": "androidx/car/ui/drawerlayout/widget/DrawerLayout(.*)",
-      "to": "androidx/drawerlayout/widget/DrawerLayout{0}"
-    },
-    {
-      "from": "androidx/car/ui/asynclayoutinflater/view/AsyncLayoutInflater(.*)",
-      "to": "androidx/asynclayoutinflater/view/AsyncLayoutInflater{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/ArrayMap(.*)",
-      "to": "androidx/collection/ArrayMap{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/ArraySet(.*)",
-      "to": "androidx/collection/ArraySet{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/CircularArray(.*)",
-      "to": "androidx/collection/CircularArray{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/CircularIntArray(.*)",
-      "to": "androidx/collection/CircularIntArray{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/ContainerHelpers(.*)",
-      "to": "androidx/collection/ContainerHelpers{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/LongSparseArray(.*)",
-      "to": "androidx/collection/LongSparseArray{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/LruCache(.*)",
-      "to": "androidx/collection/LruCache{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/MapCollections(.*)",
-      "to": "androidx/collection/MapCollections{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/SimpleArrayMap(.*)",
-      "to": "androidx/collection/SimpleArrayMap{0}"
-    },
-    {
-      "from": "androidx/car/ui/collection/SparseArray(.*)",
-      "to": "androidx/collection/SparseArray{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/(.*)",
-      "to": "androidx/core/{0}"
-    },
-    {
-      "from": "androidx/car/ui/core/(.*)",
-      "to": "androidx/core/{0}"
-    },
-    {
-      "from": "androidx/car/ui/arch/core/(.*)",
-      "to": "androidx/arch/core/{0}"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/(.*)",
-      "to": "androidx/lifecycle/{0}"
-    },
-    {
-      "from": "androidx/car/ui/arch/core/executor/testing/(.*)",
-      "to": "androidx/arch/core/executor/testing/{0}"
-    },
-    {
-      "from": "androidx/car/ui/constraintlayout/solver/(.*)",
-      "to": "androidx/constraintlayout/solver/{0}"
-    },
-    {
-      "from": "androidx/car/ui/constraintlayout/widget/(.*)",
-      "to": "androidx/constraintlayout/widget/{0}"
-    },
-    {
-      "from": "androidx/car/ui/constraintlayout/core/(.*)",
-      "to": "androidx/constraintlayout/core/{0}"
-    },
-    {
-      "from": "androidx/versionedparcelable/(.*)",
-      "to": "androidx/versionedparcelable/{0}"
-    },
-    {
-      "from": "android/support/design/widget/AppBarLayout(.*)",
-      "to": "com/google/android/material/appbar/AppBarLayout{0}"
-    },
-    {
-      "from": "android/support/design/widget/BaseTransientBottomBar(.*)",
-      "to": "com/google/android/material/snackbar/BaseTransientBottomBar{0}"
-    },
-    {
-      "from": "android/support/design/widget/BottomNavigationView(.*)",
-      "to": "com/google/android/material/bottomnavigation/BottomNavigationView{0}"
-    },
-    {
-      "from": "android/support/design/widget/BottomSheet(.*)",
-      "to": "com/google/android/material/bottomsheet/BottomSheet{0}"
-    },
-    {
-      "from": "android/support/design/widget/CheckableImageButton(.*)",
-      "to": "com/google/android/material/internal/CheckableImageButton{0}"
-    },
-    {
-      "from": "android/support/design/widget/CircularBorderDrawable(.*)",
-      "to": "com/google/android/material/internal/CircularBorderDrawable{0}"
-    },
-    {
-      "from": "android/support/design/widget/CollapsingTextHelper(.*)",
-      "to": "com/google/android/material/internal/CollapsingTextHelper{0}"
-    },
-    {
-      "from": "android/support/design/widget/CollapsingToolbarLayout(.*)",
-      "to": "com/google/android/material/appbar/CollapsingToolbarLayout{0}"
-    },
-    {
-      "from": "android/support/design/widget/CutoutDrawable(.*)",
-      "to": "com/google/android/material/textfield/CutoutDrawable{0}"
-    },
-    {
-      "from": "android/support/design/widget/DescendantOffsetUtils(.*)",
-      "to": "com/google/android/material/internal/DescendantOffsetUtils{0}"
-    },
-    {
-      "from": "android/support/design/widget/DrawableUtils(.*)",
-      "to": "com/google/android/material/internal/DrawableUtils{0}"
-    },
-    {
-      "from": "android/support/design/widget/FloatingActionButton(.*)",
-      "to": "com/google/android/material/floatingactionbutton/FloatingActionButton{0}"
-    },
-    {
-      "from": "android/support/design/widget/HeaderBehavior(.*)",
-      "to": "com/google/android/material/appbar/HeaderBehavior{0}"
-    },
-    {
-      "from": "android/support/design/widget/HeaderScrollingViewBehavior(.*)",
-      "to": "com/google/android/material/appbar/HeaderScrollingViewBehavior{0}"
-    },
-    {
-      "from": "android/support/design/widget/HideBottomViewOnScrollBehavior(.*)",
-      "to": "com/google/android/material/behavior/HeaderScrollingViewBehavior{0}"
-    },
-    {
-      "from": "android/support/design/widget/IndicatorViewController(.*)",
-      "to": "com/google/android/material/textfield/IndicatorViewController{0}"
-    },
-    {
-      "from": "android/support/design/widget/MathUtils(.*)",
-      "to": "com/google/android/material/math/MathUtils{0}"
-    },
-    {
-      "from": "android/support/design/widget/NavigationView(.*)",
-      "to": "com/google/android/material/navigation/NavigationView{0}"
-    },
-    {
-      "from": "android/support/design/widget/Shadow(.*)",
-      "to": "com/google/android/material/shadow/Shadow{0}"
-    },
-    {
-      "from": "android/support/design/widget/Snackbar(.*)",
-      "to": "com/google/android/material/snackbar/Snackbar{0}"
-    },
-    {
-      "from": "android/support/design/widget/SnackbarManager(.*)",
-      "to": "com/google/android/material/snackbar/SnackbarManager{0}"
-    },
-    {
-      "from": "android/support/design/widget/StateListAnimator(.*)",
-      "to": "com/google/android/material/internal/StateListAnimator{0}"
-    },
-    {
-      "from": "android/support/design/widget/SwipeDismissBehavior(.*)",
-      "to": "com/google/android/material/behavior/SwipeDismissBehavior{0}"
-    },
-    {
-      "from": "android/support/design/widget/Tab(.*)",
-      "to": "com/google/android/material/tabs/Tab{0}"
-    },
-    {
-      "from": "android/support/design/widget/TextInput(.*)",
-      "to": "com/google/android/material/textfield/TextInput{0}"
-    },
-    {
-      "from": "android/support/design/widget/ViewOffsetBehavior(.*)",
-      "to": "com/google/android/material/appbar/ViewOffsetBehavior{0}"
-    },
-    {
-      "from": "android/support/design/widget/ViewOffsetHelper(.*)",
-      "to": "com/google/android/material/appbar/ViewOffsetHelper{0}"
-    },
-    {
-      "from": "android/support/design/widget/ViewUtilsLollipop(.*)",
-      "to": "com/google/android/material/appbar/ViewUtilsLollipop{0}"
-    },
-    {
-      "from": "android/support/design/widget/VisibilityAwareImageButton(.*)",
-      "to": "com/google/android/material/internal/VisibilityAwareImageButton{0}"
-    },
-    {
-      "from": "android/support/design/internal/BottomNavigation(.*)",
-      "to": "com/google/android/material/bottomnavigation/BottomNavigation{0}"
-    },
-    {
-      "from": "android/support/design/internal/SnackbarContentLayout(.*)",
-      "to": "com/google/android/material/snackbar/SnackbarContentLayout{0}"
-    },
-    {
-      "from": "android/support/design/R(.*)",
-      "to": "com/google/android/material/R{0}"
-    },
-    {
-      "from": "android/support/design/(.*)",
-      "to": "com/google/android/material/{0}"
-    },
-    {
-      "from": "androidx/car/ui/test/(.*)",
-      "to": "androidx/test/{0}"
-    },
-    {
-        "from": "androidx/car/ui/annotation/(.*)",
-        "to": "androidx/annotation/{0}"
-    },
-  ],
-  "slRules": [
-    {
-      "from": "androidx/recyclerview/selection/(.*)",
-      "to": "androidx/car/ui/recyclerview/selection/{0}"
-    },
-    {
-      "from": "androidx/(.*)/ktx/(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/(.*)/(.*)-ktx/(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/(.*)/lint/(.*)",
-      "to": "androidx/car/ui/{0}/lint/{1}"
-    },
-    {
-      "from": "androidx/(.*)/(.*)-lint/(.*)",
-      "to": "androidx/car/ui/{0}/{1}-lint/{2}"
-    },
-    {
-      "from": "androidx/drawerlayout/widget/annotations",
-      "to": "androidx/car/ui/drawerlayout/widget/annotations"
-    },
-    {
-      "from": "androidx/fragment/app/annotations",
-      "to": "androidx/car/ui/fragment/app/annotations"
-    },
-    {
-      "from": "androidx/activity/result/annotations",
-      "to": "androidx/car/ui/activity/result/annotations"
-    }
-  ],
-  "packageMap": [
-    {
-      "from": "androidx/car/ui/recyclerview/selection",
-      "to": "androidx/recyclerview/selection"
-    },
-    {
-      "from": "androidx/car/ui/asynclayoutinflater",
-      "to": "androidx/asynclayoutinflater"
-    },
-    {
-      "from": "androidx/car/ui/customview",
-      "to": "androidx/customview"
-    },
-    {
-      "from": "androidx/car/ui/loader",
-      "to": "androidx/loader"
-    },
-    {
-      "from": "androidx/car/ui/appcompat",
-      "to": "androidx/appcompat"
-    },
-    {
-      "from": "androidx/car/ui/recyclerview",
-      "to": "androidx/recyclerview"
-    },
-    {
-      "from": "androidx/car/ui/preference",
-      "to": "androidx/preference"
-    },
-    {
-      "from": "androidx/car/ui/core",
-      "to": "androidx/core"
-    },
-    {
-      "from": "androidx/car/ui/fragment",
-      "to": "androidx/fragment"
-    },
-    {
-      "from": "androidx/car/ui/drawerlayout",
-      "to": "androidx/drawerlayout"
-    },
-    {
-      "from": "androidx/car/ui/arch/core/testing",
-      "to": "androidx/arch/core/testing"
-    },
-    {
-      "from": "androidx/car/ui/arch/core",
-      "to": "androidx/arch/core"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/extensions",
-      "to": "androidx/lifecycle/extensions"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/testing",
-      "to": "androidx/lifecycle/testing"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/livedata/core",
-      "to": "androidx/lifecycle/livedata/core"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle",
-      "to": "androidx/lifecycle"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/viewmodel",
-      "to": "androidx/lifecycle/viewmodel"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/livedata",
-      "to": "androidx/lifecycle/livedata"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/reactivestreams",
-      "to": "androidx/lifecycle/reactivestreams"
-    },
-    {
-      "from": "androidx/car/ui/constraintlayout/widget",
-      "to": "androidx/constraintlayout/widget"
-    },
-    {
-      "from": "androidx/car/ui/lifecycle/runtime",
-      "to": "androidx/lifecycle/runtime"
-    },
-    {
-      "from": "androidx/car/ui/appcompat/resources",
-      "to": "androidx/appcompat/resources"
-    },
-    {
-      "from": "androidx/car/ui/activity",
-      "to": "androidx/activity"
-    },
-    {
-      "from": "androidx/car/ui/savedstate",
-      "to": "androidx/savedstate"
-    },
-    {
-      "from": "androidx/car/ui/vectordrawable",
-      "to": "androidx/vectordrawable"
-    }
-  ],
-  "pomRules": [
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "animated-vector-drawable",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.vectordrawable",
-        "artifactId": "vectordrawable-animated",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "appcompat-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.appcompat",
-        "artifactId": "appcompat",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "cardview-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.cardview",
-        "artifactId": "cardview",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "customtabs",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.browser",
-        "artifactId": "browser",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "design",
-        "version": "{oldMaterialVersion}"
-      },
-      "to": {
-        "groupId": "com.google.android.material",
-        "artifactId": "material",
-        "version": "{newMaterialVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "exifinterface",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.exifinterface",
-        "artifactId": "exifinterface",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "gridlayout-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.gridlayout",
-        "artifactId": "gridlayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "leanback-v17",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.leanback",
-        "artifactId": "leanback",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "mediarouter-v7",
-        "version": "{oldMediarouterVersion}"
-      },
-      "to": {
-        "groupId": "androidx.mediarouter",
-        "artifactId": "mediarouter",
-        "version": "{newMediarouterVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "multidex",
-        "version": "1.0.3"
-      },
-      "to": {
-        "groupId": "androidx.multidex",
-        "artifactId": "multidex",
-        "version": "2.0.0"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "multidex-instrumentation",
-        "version": "1.0.3"
-      },
-      "to": {
-        "groupId": "androidx.multidex",
-        "artifactId": "multidex-instrumentation",
-        "version": "2.0.0"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "palette-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.palette",
-        "artifactId": "palette",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "percent",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.percentlayout",
-        "artifactId": "percentlayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "preference-leanback-v17",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.leanback",
-        "artifactId": "leanback-preference",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "preference-v14",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.legacy",
-        "artifactId": "legacy-preference-v14",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "preference-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.preference",
-        "artifactId": "preference",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "recommendation",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.recommendation",
-        "artifactId": "recommendation",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "recyclerview-v7",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.recyclerview",
-        "artifactId": "recyclerview",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-annotations",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.annotation",
-        "artifactId": "annotation",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-compat",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.core",
-        "artifactId": "core",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-content",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.contentpager",
-        "artifactId": "contentpager",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-core-ui",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.legacy",
-        "artifactId": "legacy-support-core-ui",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-core-utils",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.legacy",
-        "artifactId": "legacy-support-core-utils",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-dynamic-animation",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.dynamicanimation",
-        "artifactId": "dynamicanimation",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-emoji",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.emoji",
-        "artifactId": "emoji",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-emoji-appcompat",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.emoji",
-        "artifactId": "emoji-appcompat",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-emoji-bundled",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.emoji",
-        "artifactId": "emoji-bundled",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-fragment",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.fragment",
-        "artifactId": "fragment",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-media-compat",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.media",
-        "artifactId": "media",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-tv-provider",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.tvprovider",
-        "artifactId": "tvprovider",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-v13",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.legacy",
-        "artifactId": "legacy-support-v13",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-v4",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.legacy",
-        "artifactId": "legacy-support-v4",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "support-vector-drawable",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.vectordrawable",
-        "artifactId": "vectordrawable",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "textclassifier",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.textclassifier",
-        "artifactId": "textclassifier",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "transition",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.transition",
-        "artifactId": "transition",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "wear",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.wear",
-        "artifactId": "wear",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "asynclayoutinflater",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.asynclayoutinflater",
-        "artifactId": "asynclayoutinflater",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "collections",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.collection",
-        "artifactId": "collection",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "coordinatorlayout",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.coordinatorlayout",
-        "artifactId": "coordinatorlayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "cursoradapter",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.cursoradapter",
-        "artifactId": "cursoradapter",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "customview",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.customview",
-        "artifactId": "customview",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "documentfile",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.documentfile",
-        "artifactId": "documentfile",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "drawerlayout",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.drawerlayout",
-        "artifactId": "drawerlayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "interpolator",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.interpolator",
-        "artifactId": "interpolator",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "loader",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.loader",
-        "artifactId": "loader",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "localbroadcastmanager",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.localbroadcastmanager",
-        "artifactId": "localbroadcastmanager",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "print",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.print",
-        "artifactId": "print",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "slidingpanelayout",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.slidingpanelayout",
-        "artifactId": "slidingpanelayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "swiperefreshlayout",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.swiperefreshlayout",
-        "artifactId": "swiperefreshlayout",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "viewpager",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.viewpager",
-        "artifactId": "viewpager",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.databinding",
-        "artifactId": "adapters",
-        "version": "undefined"
-      },
-      "to": {
-        "groupId": "androidx.databinding",
-        "artifactId": "databinding-adapters",
-        "version": "{newDataBindingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.databinding",
-        "artifactId": "baseLibrary",
-        "version": "undefined"
-      },
-      "to": {
-        "groupId": "androidx.databinding",
-        "artifactId": "databinding-common",
-        "version": "{newDataBindingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.databinding",
-        "artifactId": "compiler",
-        "version": "undefined"
-      },
-      "to": {
-        "groupId": "androidx.databinding",
-        "artifactId": "databinding-compiler",
-        "version": "{newDataBindingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.databinding",
-        "artifactId": "compilerCommon",
-        "version": "undefined"
-      },
-      "to": {
-        "groupId": "androidx.databinding",
-        "artifactId": "databinding-compiler-common",
-        "version": "{newDataBindingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.databinding",
-        "artifactId": "library",
-        "version": "undefined"
-      },
-      "to": {
-        "groupId": "androidx.databinding",
-        "artifactId": "databinding-runtime",
-        "version": "{newDataBindingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "versionedparcelable",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.versionedparcelable",
-        "artifactId": "versionedparcelable",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.work",
-        "artifactId": "work-runtime",
-        "version": "{oldWorkManagerVersion}"
-      },
-      "to": {
-        "groupId": "androidx.work",
-        "artifactId": "work-runtime",
-        "version": "{newWorkManagerVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.work",
-        "artifactId": "work-runtime-ktx",
-        "version": "{oldWorkManagerVersion}"
-      },
-      "to": {
-        "groupId": "androidx.work",
-        "artifactId": "work-runtime-ktx",
-        "version": "{newWorkManagerVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.work",
-        "artifactId": "work-rxjava2",
-        "version": "{oldWorkManagerVersion}"
-      },
-      "to": {
-        "groupId": "androidx.work",
-        "artifactId": "work-rxjava2",
-        "version": "{newWorkManagerVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.work",
-        "artifactId": "work-testing",
-        "version": "{oldWorkManagerVersion}"
-      },
-      "to": {
-        "groupId": "androidx.work",
-        "artifactId": "work-testing",
-        "version": "{newWorkManagerVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-common",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-common",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-common-ktx",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-common-ktx",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-fragment",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-fragment",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-fragment-ktx",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-fragment-ktx",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-runtime",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-runtime",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-runtime-ktx",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-runtime-ktx",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-ui",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-ui",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.navigation",
-        "artifactId": "navigation-ui-ktx",
-        "version": "{oldNavigationVersion}"
-      },
-      "to": {
-        "groupId": "androidx.navigation",
-        "artifactId": "navigation-ui-ktx",
-        "version": "{newNavigationVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.core",
-        "artifactId": "common",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.arch.core",
-        "artifactId": "core-common",
-        "version": "{newArchCoreVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.core",
-        "artifactId": "core",
-        "version": "1.0.0-alpha3"
-      },
-      "to": {
-        "groupId": "androidx.arch.core",
-        "artifactId": "core",
-        "version": "{newArchCoreVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.core",
-        "artifactId": "core-testing",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.arch.core",
-        "artifactId": "core-testing",
-        "version": "{newArchCoreVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.core",
-        "artifactId": "runtime",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.arch.core",
-        "artifactId": "core-runtime",
-        "version": "{newArchCoreVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "common",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-common",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "common-java8",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-common-java8",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "compiler",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-compiler",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "extensions",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-extensions",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "reactivestreams",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-reactivestreams",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "runtime",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-runtime",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "viewmodel",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-viewmodel",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "livedata",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-livedata",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.lifecycle",
-        "artifactId": "livedata-core",
-        "version": "1.1.1"
-      },
-      "to": {
-        "groupId": "androidx.lifecycle",
-        "artifactId": "lifecycle-livedata-core",
-        "version": "{newLifecycleVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.paging",
-        "artifactId": "common",
-        "version": "1.0.0"
-      },
-      "to": {
-        "groupId": "androidx.paging",
-        "artifactId": "paging-common",
-        "version": "{newPagingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.paging",
-        "artifactId": "runtime",
-        "version": "1.0.0"
-      },
-      "to": {
-        "groupId": "androidx.paging",
-        "artifactId": "paging-runtime",
-        "version": "{newPagingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.paging",
-        "artifactId": "rxjava2",
-        "version": "1.0.0-alpha1"
-      },
-      "to": {
-        "groupId": "androidx.paging",
-        "artifactId": "paging-rxjava2",
-        "version": "{newPagingVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence",
-        "artifactId": "db",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.sqlite",
-        "artifactId": "sqlite",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence",
-        "artifactId": "db-framework",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.sqlite",
-        "artifactId": "sqlite-framework",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "common",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-common",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "compiler",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-compiler",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "migration",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-migration",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "runtime",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-runtime",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "rxjava2",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-rxjava2",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "testing",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-testing",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "android.arch.persistence.room",
-        "artifactId": "guava",
-        "version": "{oldRoomVersion}"
-      },
-      "to": {
-        "groupId": "androidx.room",
-        "artifactId": "room-guava",
-        "version": "{newRoomVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.constraint",
-        "artifactId": "constraint-layout",
-        "version": "1.1.0"
-      },
-      "to": {
-        "groupId": "androidx.constraintlayout",
-        "artifactId": "constraintlayout",
-        "version": "1.1.3"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.constraint",
-        "artifactId": "constraint-layout-solver",
-        "version": "1.1.0"
-      },
-      "to": {
-        "groupId": "androidx.constraintlayout",
-        "artifactId": "constraintlayout-solver",
-        "version": "1.1.3"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test",
-        "artifactId": "orchestrator",
-        "version": "1.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test",
-        "artifactId": "orchestrator",
-        "version": "{newTestsVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test",
-        "artifactId": "rules",
-        "version": "1.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test",
-        "artifactId": "rules",
-        "version": "{newTestsVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test",
-        "artifactId": "runner",
-        "version": "1.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test",
-        "artifactId": "runner",
-        "version": "{newTestsVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test",
-        "artifactId": "monitor",
-        "version": "1.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test",
-        "artifactId": "monitor",
-        "version": "{newTestsVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-accessibility",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-accessibility",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-contrib",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-contrib",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-core",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-core",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-idling-resource",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-idling-resource",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-intents",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-intents",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-remote",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-remote",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso",
-        "artifactId": "espresso-web",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso",
-        "artifactId": "espresso-web",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso.idling",
-        "artifactId": "idling-concurrent",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso.idling",
-        "artifactId": "idling-concurrent",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.espresso.idling",
-        "artifactId": "idling-net",
-        "version": "3.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test.espresso.idling",
-        "artifactId": "idling-net",
-        "version": "{newEspressoVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.janktesthelper",
-        "artifactId": "janktesthelper",
-        "version": "1.0.1"
-      },
-      "to": {
-        "groupId": "androidx.test.jank",
-        "artifactId": "janktesthelper",
-        "version": "{newJankTestHelperVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.services",
-        "artifactId": "test-services",
-        "version": "1.0.2"
-      },
-      "to": {
-        "groupId": "androidx.test",
-        "artifactId": "test-services",
-        "version": "{newTestsVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support.test.uiautomator",
-        "artifactId": "uiautomator",
-        "version": "2.1.3"
-      },
-      "to": {
-        "groupId": "androidx.test.uiautomator",
-        "artifactId": "uiautomator",
-        "version": "{newUiAutomatorVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "car",
-        "version": "{oldCarVersion}"
-      },
-      "to": {
-        "groupId": "androidx.car",
-        "artifactId": "car",
-        "version": "{newCarVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "slices-core",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.slice",
-        "artifactId": "slice-core",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "slices-builders",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.slice",
-        "artifactId": "slice-builders",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "slices-view",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.slice",
-        "artifactId": "slice-view",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "heifwriter",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.heifwriter",
-        "artifactId": "heifwriter",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "recyclerview-selection",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.recyclerview",
-        "artifactId": "recyclerview-selection",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "webkit",
-        "version": "{oldSlVersion}"
-      },
-      "to": {
-        "groupId": "androidx.webkit",
-        "artifactId": "webkit",
-        "version": "{newSlVersion}"
-      }
-    },
-    {
-      "from": {
-        "groupId": "com.android.support",
-        "artifactId": "biometric",
-        "version": "{oldBiometricVersion}"
-      },
-      "to": {
-        "groupId": "androidx.biometric",
-        "artifactId": "biometric",
-        "version": "{newBiometricVersion}"
-      }
-    }
-  ],
-  "versions": {
-    "latestReleased": {
-      "oldSlVersion": "28.0.0",
-      "oldMaterialVersion": "28.0.0",
-      "oldRoomVersion": "1.1.0",
-      "oldCarVersion": "28.0.0-alpha5",
-      "oldMediarouterVersion": "28.0.0-alpha5",
-      "oldMedia2Version": "28.0.0-alpha03",
-      "oldExoplayerVersion": "28.0.0-alpha01",
-      "oldBiometricVersion": "28.0.0-alpha03",
-      "oldNavigationVersion": "1.0.0",
-      "oldWorkManagerVersion": "1.0.0",
-      "newSlVersion": "1.0.0",
-      "newMaterialVersion": "1.0.0",
-      "newArchCoreVersion": "2.0.0",
-      "newLifecycleVersion": "2.0.0",
-      "newPagingVersion": "2.0.0",
-      "newRoomVersion": "2.0.0",
-      "newEspressoVersion": "3.1.0-alpha3",
-      "newTestsVersion": "1.1.0-alpha3",
-      "newJankTestHelperVersion": "1.0.1-alpha3",
-      "newUiAutomatorVersion": "2.2.0-alpha3",
-      "newCarVersion": "1.0.0-alpha5",
-      "newMediarouterVersion": "1.0.0-alpha5",
-      "newMedia2Version": "1.0.0-alpha03",
-      "newExoplayerVersion": "1.0.0-alpha01",
-      "newBiometricVersion": "1.0.0-alpha03",
-      "newDataBindingVersion": "undefined",
-      "newNavigationVersion": "2.0.0",
-      "newWorkManagerVersion": "2.0.0"
-    }
-  },
-  "map": {
-    "types": {
-      "androidx/car/ui/arch/core/executor/AppToolkitTaskExecutor": "androidx/arch/core/executor/AppToolkitTaskExecutor",
-      "androidx/car/ui/arch/core/executor/ArchTaskExecutor": "androidx/arch/core/executor/ArchTaskExecutor",
-      "androidx/car/ui/arch/core/executor/DefaultTaskExecutor": "androidx/arch/core/executor/DefaultTaskExecutor",
-      "androidx/car/ui/arch/core/executor/JunitTaskExecutorRule": "androidx/arch/core/executor/JunitTaskExecutorRule",
-      "androidx/car/ui/arch/core/executor/TaskExecutor": "androidx/arch/core/executor/TaskExecutor",
-      "androidx/car/ui/arch/core/executor/TaskExecutorWithFakeMainThread": "androidx/arch/core/executor/TaskExecutorWithFakeMainThread",
-      "androidx/car/ui/arch/core/executor/testing/CountingTaskExecutorRule": "androidx/arch/core/executor/testing/CountingTaskExecutorRule",
-      "androidx/car/ui/arch/core/executor/testing/InstantTaskExecutorRule": "androidx/arch/core/executor/testing/InstantTaskExecutorRule",
-      "androidx/car/ui/arch/core/internal/FastSafeIterableMap": "androidx/arch/core/internal/FastSafeIterableMap",
-      "androidx/car/ui/arch/core/internal/SafeIterableMap": "androidx/arch/core/internal/SafeIterableMap",
-      "androidx/car/ui/arch/core/util/Function": "androidx/arch/core/util/Function",
-      "androidx/car/ui/lifecycle/AndroidViewModel": "androidx/lifecycle/AndroidViewModel",
-      "androidx/car/ui/lifecycle/ClassesInfoCache": "androidx/lifecycle/ClassesInfoCache",
-      "androidx/car/ui/lifecycle/CompositeGeneratedAdaptersObserver": "androidx/lifecycle/CompositeGeneratedAdaptersObserver",
-      "androidx/car/ui/lifecycle/ComputableLiveData": "androidx/lifecycle/ComputableLiveData",
-      "androidx/car/ui/lifecycle/DefaultLifecycleObserver": "androidx/lifecycle/DefaultLifecycleObserver",
-      "androidx/car/ui/lifecycle/Elements_extKt": "androidx/lifecycle/Elements_extKt",
-      "androidx/car/ui/lifecycle/EmptyActivityLifecycleCallbacks": "androidx/lifecycle/EmptyActivityLifecycleCallbacks",
-      "androidx/car/ui/lifecycle/ErrorMessages": "androidx/lifecycle/ErrorMessages",
-      "androidx/car/ui/lifecycle/FullLifecycleObserver": "androidx/lifecycle/FullLifecycleObserver",
-      "androidx/car/ui/lifecycle/FullLifecycleObserverAdapter": "androidx/lifecycle/FullLifecycleObserverAdapter",
-      "androidx/car/ui/lifecycle/GeneratedAdapter": "androidx/lifecycle/GeneratedAdapter",
-      "androidx/car/ui/lifecycle/GenericLifecycleObserver": "androidx/lifecycle/GenericLifecycleObserver",
-      "androidx/car/ui/lifecycle/HolderFragment": "androidx/lifecycle/HolderFragment",
-      "androidx/car/ui/lifecycle/Input_collectorKt": "androidx/lifecycle/Input_collectorKt",
-      "androidx/car/ui/lifecycle/Lifecycle": "androidx/lifecycle/Lifecycle",
-      "androidx/car/ui/lifecycle/LifecycleDispatcher": "androidx/lifecycle/LifecycleDispatcher",
-      "androidx/car/ui/lifecycle/LifecycleObserver": "androidx/lifecycle/LifecycleObserver",
-      "androidx/car/ui/lifecycle/LifecycleOwner": "androidx/lifecycle/LifecycleOwner",
-      "androidx/car/ui/lifecycle/LifecycleProcessor": "androidx/lifecycle/LifecycleProcessor",
-      "androidx/car/ui/lifecycle/LifecycleRegistry": "androidx/lifecycle/LifecycleRegistry",
-      "androidx/car/ui/lifecycle/LifecycleRegistryOwner": "androidx/lifecycle/LifecycleRegistryOwner",
-      "androidx/car/ui/lifecycle/LifecycleService": "androidx/lifecycle/LifecycleService",
-      "androidx/car/ui/lifecycle/Lifecycling": "androidx/lifecycle/Lifecycling",
-      "androidx/car/ui/lifecycle/LiveData": "androidx/lifecycle/LiveData",
-      "androidx/car/ui/lifecycle/LiveDataReactiveStreams": "androidx/lifecycle/LiveDataReactiveStreams",
-      "androidx/car/ui/lifecycle/MediatorLiveData": "androidx/lifecycle/MediatorLiveData",
-      "androidx/car/ui/lifecycle/MethodCallsLogger": "androidx/lifecycle/MethodCallsLogger",
-      "androidx/car/ui/lifecycle/MutableLiveData": "androidx/lifecycle/MutableLiveData",
-      "androidx/car/ui/lifecycle/Observer": "androidx/lifecycle/Observer",
-      "androidx/car/ui/lifecycle/ObserversCollector": "androidx/lifecycle/ObserversCollector",
-      "androidx/car/ui/lifecycle/OnLifecycleEvent": "androidx/lifecycle/OnLifecycleEvent",
-      "androidx/car/ui/lifecycle/ProcessLifecycleOwner": "androidx/lifecycle/ProcessLifecycleOwner",
-      "androidx/car/ui/lifecycle/ProcessLifecycleOwnerInitializer": "androidx/lifecycle/ProcessLifecycleOwnerInitializer",
-      "androidx/car/ui/lifecycle/ReflectiveGenericLifecycleObserver": "androidx/lifecycle/ReflectiveGenericLifecycleObserver",
-      "androidx/car/ui/lifecycle/ReportFragment": "androidx/lifecycle/ReportFragment",
-      "androidx/car/ui/lifecycle/ServiceLifecycleDispatcher": "androidx/lifecycle/ServiceLifecycleDispatcher",
-      "androidx/car/ui/lifecycle/SingleGeneratedAdapterObserver": "androidx/lifecycle/SingleGeneratedAdapterObserver",
-      "androidx/car/ui/lifecycle/TransformationKt": "androidx/lifecycle/TransformationKt",
-      "androidx/car/ui/lifecycle/Transformations": "androidx/lifecycle/Transformations",
-      "androidx/car/ui/lifecycle/Validator": "androidx/lifecycle/Validator",
-      "androidx/car/ui/lifecycle/ViewModel": "androidx/lifecycle/ViewModel",
-      "androidx/car/ui/lifecycle/ViewModelProvider": "androidx/lifecycle/ViewModelProvider",
-      "androidx/car/ui/lifecycle/ViewModelProviders": "androidx/lifecycle/ViewModelProviders",
-      "androidx/car/ui/lifecycle/ViewModelStore": "androidx/lifecycle/ViewModelStore",
-      "androidx/car/ui/lifecycle/ViewModelStoreOwner": "androidx/lifecycle/ViewModelStoreOwner",
-      "androidx/car/ui/lifecycle/ViewModelStores": "androidx/lifecycle/ViewModelStores",
-      "androidx/car/ui/lifecycle/WriterKt": "androidx/lifecycle/WriterKt",
-      "androidx/car/ui/lifecycle/model/AdapterClass": "androidx/lifecycle/model/AdapterClass",
-      "androidx/car/ui/lifecycle/model/AdapterClassKt": "androidx/lifecycle/model/AdapterClassKt",
-      "androidx/car/ui/lifecycle/model/EventMethod": "androidx/lifecycle/model/EventMethod",
-      "androidx/car/ui/lifecycle/model/EventMethodCall": "androidx/lifecycle/model/EventMethodCall",
-      "androidx/car/ui/lifecycle/model/InputModel": "androidx/lifecycle/model/InputModel",
-      "androidx/car/ui/lifecycle/model/LifecycleObserverInfo": "androidx/lifecycle/model/LifecycleObserverInfo",
-      "androidx/car/ui/annotation/AnimRes": "androidx/annotation/AnimRes",
-      "androidx/car/ui/annotation/AnimatorRes": "androidx/annotation/AnimatorRes",
-      "androidx/car/ui/annotation/AnyRes": "androidx/annotation/AnyRes",
-      "androidx/car/ui/annotation/AnyThread": "androidx/annotation/AnyThread",
-      "androidx/car/ui/annotation/ArrayRes": "androidx/annotation/ArrayRes",
-      "androidx/car/ui/annotation/AttrRes": "androidx/annotation/AttrRes",
-      "androidx/car/ui/annotation/BinderThread": "androidx/annotation/BinderThread",
-      "androidx/car/ui/annotation/BoolRes": "androidx/annotation/BoolRes",
-      "androidx/car/ui/annotation/CallSuper": "androidx/annotation/CallSuper",
-      "androidx/car/ui/annotation/CheckResult": "androidx/annotation/CheckResult",
-      "androidx/car/ui/annotation/ColorInt": "androidx/annotation/ColorInt",
-      "androidx/car/ui/annotation/ColorLong": "androidx/annotation/ColorLong",
-      "androidx/car/ui/annotation/ColorRes": "androidx/annotation/ColorRes",
-      "androidx/car/ui/annotation/DimenRes": "androidx/annotation/DimenRes",
-      "androidx/car/ui/annotation/Dimension": "androidx/annotation/Dimension",
-      "androidx/car/ui/annotation/DrawableRes": "androidx/annotation/DrawableRes",
-      "androidx/car/ui/annotation/FloatRange": "androidx/annotation/FloatRange",
-      "androidx/car/ui/annotation/FontRes": "androidx/annotation/FontRes",
-      "androidx/car/ui/annotation/FractionRes": "androidx/annotation/FractionRes",
-      "androidx/car/ui/annotation/GuardedBy": "androidx/annotation/GuardedBy",
-      "androidx/car/ui/annotation/HalfFloat": "androidx/annotation/HalfFloat",
-      "androidx/car/ui/annotation/IdRes": "androidx/annotation/IdRes",
-      "androidx/car/ui/annotation/IntDef": "androidx/annotation/IntDef",
-      "androidx/car/ui/annotation/IntRange": "androidx/annotation/IntRange",
-      "androidx/car/ui/annotation/IntegerRes": "androidx/annotation/IntegerRes",
-      "androidx/car/ui/annotation/InterpolatorRes": "androidx/annotation/InterpolatorRes",
-      "androidx/car/ui/annotation/Keep": "androidx/annotation/Keep",
-      "androidx/car/ui/annotation/LayoutRes": "androidx/annotation/LayoutRes",
-      "androidx/car/ui/annotation/LongDef": "androidx/annotation/LongDef",
-      "androidx/car/ui/annotation/MainThread": "androidx/annotation/MainThread",
-      "androidx/car/ui/annotation/MenuRes": "androidx/annotation/MenuRes",
-      "androidx/car/ui/annotation/NavigationRes": "androidx/annotation/NavigationRes",
-      "androidx/car/ui/annotation/NonNull": "androidx/annotation/NonNull",
-      "androidx/car/ui/annotation/Nullable": "androidx/annotation/Nullable",
-      "androidx/car/ui/annotation/PluralsRes": "androidx/annotation/PluralsRes",
-      "androidx/car/ui/annotation/Px": "androidx/annotation/Px",
-      "androidx/car/ui/annotation/RawRes": "androidx/annotation/RawRes",
-      "androidx/car/ui/annotation/RequiresApi": "androidx/annotation/RequiresApi",
-      "androidx/car/ui/annotation/RequiresFeature": "androidx/annotation/RequiresFeature",
-      "androidx/car/ui/annotation/RequiresPermission": "androidx/annotation/RequiresPermission",
-      "androidx/car/ui/annotation/RestrictTo": "androidx/annotation/RestrictTo",
-      "androidx/car/ui/annotation/Size": "androidx/annotation/Size",
-      "androidx/car/ui/annotation/StringDef": "androidx/annotation/StringDef",
-      "androidx/car/ui/annotation/StringRes": "androidx/annotation/StringRes",
-      "androidx/car/ui/annotation/StyleRes": "androidx/annotation/StyleRes",
-      "androidx/car/ui/annotation/StyleableRes": "androidx/annotation/StyleableRes",
-      "androidx/car/ui/annotation/TransitionRes": "androidx/annotation/TransitionRes",
-      "androidx/car/ui/annotation/UiThread": "androidx/annotation/UiThread",
-      "androidx/car/ui/annotation/VisibleForTesting": "androidx/annotation/VisibleForTesting",
-      "androidx/car/ui/annotation/WorkerThread": "androidx/annotation/WorkerThread",
-      "androidx/car/ui/annotation/XmlRes": "androidx/annotation/XmlRes",
-      "android/support/app/recommendation/ContentRecommendation": "androidx/recommendation/app/ContentRecommendation",
-      "android/support/app/recommendation/RecommendationExtender": "androidx/recommendation/app/RecommendationExtender",
-      "androidx/car/ui/core/R": "androidx/core/R",
-      "androidx/car/ui/constraintlayout/widget/Barrier": "androidx/constraintlayout/widget/Barrier",
-      "androidx/car/ui/constraintlayout/widget/ConstraintHelper": "androidx/constraintlayout/widget/ConstraintHelper",
-      "androidx/car/ui/constraintlayout/widget/ConstraintLayout": "androidx/constraintlayout/widget/ConstraintLayout",
-      "androidx/car/ui/constraintlayout/widget/ConstraintSet": "androidx/constraintlayout/widget/ConstraintSet",
-      "androidx/car/ui/constraintlayout/widget/Constraints": "androidx/constraintlayout/widget/Constraints",
-      "androidx/car/ui/constraintlayout/widget/Group": "androidx/constraintlayout/widget/Group",
-      "androidx/car/ui/constraintlayout/widget/Guideline": "androidx/constraintlayout/widget/Guideline",
-      "androidx/car/ui/constraintlayout/widget/Placeholder": "androidx/constraintlayout/widget/Placeholder",
-      "androidx/car/ui/constraintlayout/widget/R": "androidx/constraintlayout/widget/R",
-      "androidx/car/ui/constraintlayout/solver/ArrayLinkedVariables": "androidx/constraintlayout/solver/ArrayLinkedVariables",
-      "androidx/car/ui/constraintlayout/solver/ArrayRow": "androidx/constraintlayout/solver/ArrayRow",
-      "androidx/car/ui/constraintlayout/solver/Cache": "androidx/constraintlayout/solver/Cache",
-      "androidx/car/ui/constraintlayout/solver/GoalRow": "androidx/constraintlayout/solver/GoalRow",
-      "androidx/car/ui/constraintlayout/solver/LinearSystem": "androidx/constraintlayout/solver/LinearSystem",
-      "androidx/car/ui/constraintlayout/solver/Metrics": "androidx/constraintlayout/solver/Metrics",
-      "androidx/car/ui/constraintlayout/solver/Pools": "androidx/constraintlayout/solver/Pools",
-      "androidx/car/ui/constraintlayout/solver/SolverVariable": "androidx/constraintlayout/solver/SolverVariable",
-      "androidx/car/ui/constraintlayout/solver/widgets/Analyzer": "androidx/constraintlayout/solver/widgets/Analyzer",
-      "androidx/car/ui/constraintlayout/solver/widgets/Barrier": "androidx/constraintlayout/solver/widgets/Barrier",
-      "androidx/car/ui/constraintlayout/solver/widgets/Chain": "androidx/constraintlayout/solver/widgets/Chain",
-      "androidx/car/ui/constraintlayout/solver/widgets/ChainHead": "androidx/constraintlayout/solver/widgets/ChainHead",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintAnchor": "androidx/constraintlayout/solver/widgets/ConstraintAnchor",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintHorizontalLayout": "androidx/constraintlayout/solver/widgets/ConstraintHorizontalLayout",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintTableLayout": "androidx/constraintlayout/solver/widgets/ConstraintTableLayout",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintWidget": "androidx/constraintlayout/solver/widgets/ConstraintWidget",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintWidgetContainer": "androidx/constraintlayout/solver/widgets/ConstraintWidgetContainer",
-      "androidx/car/ui/constraintlayout/solver/widgets/ConstraintWidgetGroup": "androidx/constraintlayout/solver/widgets/ConstraintWidgetGroup",
-      "androidx/car/ui/constraintlayout/solver/widgets/Guideline": "androidx/constraintlayout/solver/widgets/Guideline",
-      "androidx/car/ui/constraintlayout/solver/widgets/Helper": "androidx/constraintlayout/solver/widgets/Helper",
-      "androidx/car/ui/constraintlayout/solver/widgets/Optimizer": "androidx/constraintlayout/solver/widgets/Optimizer",
-      "androidx/car/ui/constraintlayout/solver/widgets/Rectangle": "androidx/constraintlayout/solver/widgets/Rectangle",
-      "androidx/car/ui/constraintlayout/solver/widgets/ResolutionAnchor": "androidx/constraintlayout/solver/widgets/ResolutionAnchor",
-      "androidx/car/ui/constraintlayout/solver/widgets/ResolutionDimension": "androidx/constraintlayout/solver/widgets/ResolutionDimension",
-      "androidx/car/ui/constraintlayout/solver/widgets/ResolutionNode": "androidx/constraintlayout/solver/widgets/ResolutionNode",
-      "androidx/car/ui/constraintlayout/solver/widgets/Snapshot": "androidx/constraintlayout/solver/widgets/Snapshot",
-      "androidx/car/ui/constraintlayout/solver/widgets/WidgetContainer": "androidx/constraintlayout/solver/widgets/WidgetContainer",
-      "androidx/car/ui/constraintlayout/helper/widget/Flow": "androidx/constraintlayout/helper/widget/Flow",
-      "androidx/car/ui/constraintlayout/helper/widget/Layer": "androidx/constraintlayout/helper/widget/Layer",
-      "androidx/car/ui/constraintlayout/motion/utils/ArcCurveFit": "androidx/constraintlayout/motion/utils/ArcCurveFit",
-      "androidx/car/ui/constraintlayout/motion/utils/CurveFit": "androidx/constraintlayout/motion/utils/CurveFit",
-      "androidx/car/ui/constraintlayout/motion/utils/Easing": "androidx/constraintlayout/motion/utils/Easing",
-      "androidx/car/ui/constraintlayout/motion/utils/HyperSpline": "androidx/constraintlayout/motion/utils/HyperSpline",
-      "androidx/car/ui/constraintlayout/motion/utils/LinearCurveFit": "androidx/constraintlayout/motion/utils/LinearCurveFit",
-      "androidx/car/ui/constraintlayout/motion/utils/MonotonicCurveFit": "androidx/constraintlayout/motion/utils/MonotonicCurveFit",
-      "androidx/car/ui/constraintlayout/motion/utils/Oscillator": "androidx/constraintlayout/motion/utils/Oscillator",
-      "androidx/car/ui/constraintlayout/motion/utils/StopLogic": "androidx/constraintlayout/motion/utils/StopLogic",
-      "androidx/car/ui/constraintlayout/motion/utils/VelocityMatrix": "androidx/constraintlayout/motion/utils/VelocityMatrix",
-      "androidx/car/ui/constraintlayout/motion/widget/Animatable": "androidx/constraintlayout/motion/widget/Animatable",
-      "androidx/car/ui/constraintlayout/motion/widget/CustomFloatAttributes": "androidx/constraintlayout/motion/widget/CustomFloatAttributes",
-      "androidx/car/ui/constraintlayout/motion/widget/Debug": "androidx/constraintlayout/motion/widget/Debug",
-      "androidx/car/ui/constraintlayout/motion/widget/DesignTool": "androidx/constraintlayout/motion/widget/DesignTool",
-      "androidx/car/ui/constraintlayout/motion/widget/Key": "androidx/constraintlayout/motion/widget/Key",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyAttributes": "androidx/constraintlayout/motion/widget/KeyAttributes",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyCache": "androidx/constraintlayout/motion/widget/KeyCache",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyCycle": "androidx/constraintlayout/motion/widget/KeyCycle",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyCycleOscillator": "androidx/constraintlayout/motion/widget/KeyCycleOscillator",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyFrames": "androidx/constraintlayout/motion/widget/KeyFrames",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyPosition": "androidx/constraintlayout/motion/widget/KeyPosition",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyPositionBase": "androidx/constraintlayout/motion/widget/KeyPositionBase",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyTimeCycle": "androidx/constraintlayout/motion/widget/KeyTimeCycle",
-      "androidx/car/ui/constraintlayout/motion/widget/KeyTrigger": "androidx/constraintlayout/motion/widget/KeyTrigger",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionConstrainedPoint": "androidx/constraintlayout/motion/widget/MotionConstrainedPoint",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionController": "androidx/constraintlayout/motion/widget/MotionController",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionHelper": "androidx/constraintlayout/motion/widget/MotionHelper",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionInterpolator": "androidx/constraintlayout/motion/widget/MotionInterpolator",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionPaths": "androidx/constraintlayout/motion/widget/MotionPaths",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionScene": "androidx/constraintlayout/motion/widget/MotionScene",
-      "androidx/car/ui/constraintlayout/motion/widget/ProxyInterface": "androidx/constraintlayout/motion/widget/ProxyInterface",
-      "androidx/car/ui/constraintlayout/motion/widget/SplineSet": "androidx/constraintlayout/motion/widget/SplineSet",
-      "androidx/car/ui/constraintlayout/motion/widget/TimeCycleSplineSet": "androidx/constraintlayout/motion/widget/TimeCycleSplineSet",
-      "androidx/car/ui/constraintlayout/motion/widget/TouchResponse": "androidx/constraintlayout/motion/widget/TouchResponse",
-      "androidx/car/ui/constraintlayout/motion/widget/TransitionAdapter": "androidx/constraintlayout/motion/widget/TransitionAdapter",
-      "androidx/car/ui/constraintlayout/motion/widget/TransitionBuilder": "androidx/constraintlayout/motion/widget/TransitionBuilder",
-      "androidx/car/ui/constraintlayout/motion/widget/MotionLayout": "androidx/constraintlayout/motion/widget/MotionLayout",
-      "androidx/car/ui/constraintlayout/utils/widget/MockView": "androidx/constraintlayout/utils/widget/MockView",
-      "androidx/car/ui/constraintlayout/utils/widget/MotionTelltales": "androidx/constraintlayout/utils/widget/MotionTelltales",
-      "androidx/car/ui/constraintlayout/core/ArrayLinkedVariables": "androidx/constraintlayout/core/ArrayLinkedVariables",
-      "androidx/car/ui/constraintlayout/core/ArrayRow": "androidx/constraintlayout/core/ArrayRow",
-      "androidx/car/ui/constraintlayout/core/Cache": "androidx/constraintlayout/core/Cache",
-      "androidx/car/ui/constraintlayout/core/GoalRow": "androidx/constraintlayout/core/GoalRow",
-      "androidx/car/ui/constraintlayout/core/LinearSystem": "androidx/constraintlayout/core/LinearSystem",
-      "androidx/car/ui/constraintlayout/core/Metrics": "androidx/constraintlayout/core/Metrics",
-      "androidx/car/ui/constraintlayout/core/Pools": "androidx/constraintlayout/core/Pools",
-      "androidx/car/ui/constraintlayout/core/SolverVariable": "androidx/constraintlayout/core/SolverVariable",
-      "androidx/car/ui/constraintlayout/core/state/ConstraintReference": "androidx/constraintlayout/core/state/ConstraintReference",
-      "androidx/car/ui/constraintlayout/core/state/Dimension": "androidx/constraintlayout/core/state/Dimension",
-      "androidx/car/ui/constraintlayout/core/state/HelperReference": "androidx/constraintlayout/core/state/HelperReference",
-      "androidx/car/ui/constraintlayout/core/state/Reference": "androidx/constraintlayout/core/state/Reference",
-      "androidx/car/ui/constraintlayout/core/state/State": "androidx/constraintlayout/core/state/State",
-      "androidx/car/ui/constraintlayout/core/state/helpers/AlignHorizontallyReference": "androidx/constraintlayout/core/state/helpers/AlignHorizontallyReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/AlignVerticallyReference": "androidx/constraintlayout/core/state/helpers/AlignVerticallyReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/BarrierReference": "androidx/constraintlayout/core/state/helpers/BarrierReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/ChainReference": "androidx/constraintlayout/core/state/helpers/ChainReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/GuidelineReference": "androidx/constraintlayout/core/state/helpers/GuidelineReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/HorizontalChainReference": "androidx/constraintlayout/core/state/helpers/HorizontalChainReference",
-      "androidx/car/ui/constraintlayout/core/state/helpers/VerticalChainReference": "androidx/constraintlayout/core/state/helpers/VerticalChainReference",
-      "androidx/car/ui/constraintlayout/core/widget/Barrier": "androidx/constraintlayout/core/widget/Barrier",
-      "androidx/car/ui/constraintlayout/core/widget/Chain": "androidx/constraintlayout/core/widget/Chain",
-      "androidx/car/ui/constraintlayout/core/widget/ChainHead": "androidx/constraintlayout/core/widget/ChainHead",
-      "androidx/car/ui/constraintlayout/core/widget/ConstraintAnchor": "androidx/constraintlayout/core/widget/ConstraintAnchor",
-      "androidx/car/ui/constraintlayout/core/widget/ConstraintWidget": "androidx/constraintlayout/core/widget/ConstraintWidget",
-      "androidx/car/ui/constraintlayout/core/widget/ConstraintWidgetContainer": "androidx/constraintlayout/core/widget/ConstraintWidgetContainer",
-      "androidx/car/ui/constraintlayout/core/widget/Flow": "androidx/constraintlayout/core/widget/Flow",
-      "androidx/car/ui/constraintlayout/core/widget/Guideline": "androidx/constraintlayout/core/widget/Guideline",
-      "androidx/car/ui/constraintlayout/core/widget/Helper": "androidx/constraintlayout/core/widget/Helper",
-      "androidx/car/ui/constraintlayout/core/widget/HelperWidget": "androidx/constraintlayout/core/widget/HelperWidget",
-      "androidx/car/ui/constraintlayout/core/widget/Optimizer": "androidx/constraintlayout/core/widget/Optimizer",
-      "androidx/car/ui/constraintlayout/core/widget/Rectangle": "androidx/constraintlayout/core/widget/Rectangle",
-      "androidx/car/ui/constraintlayout/core/widget/VirtualLayout": "androidx/constraintlayout/core/widget/VirtualLayout",
-      "androidx/car/ui/constraintlayout/core/widget/WidgetContainer": "androidx/constraintlayout/core/widget/WidgetContainer",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/BaselineDimensionDependency": "androidx/constraintlayout/core/widget/analyzer/BaselineDimensionDependency",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/BasicMeasure": "androidx/constraintlayout/core/widget/analyzer/BasicMeasure",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/ChainRun": "androidx/constraintlayout/core/widget/analyzer/ChainRun",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/Dependency": "androidx/constraintlayout/core/widget/analyzer/Dependency",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/DependencyGraph": "androidx/constraintlayout/core/widget/analyzer/DependencyGraph",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/DependencyNode": "androidx/constraintlayout/core/widget/analyzer/DependencyNode",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/DimensionDependency": "androidx/constraintlayout/core/widget/analyzer/DimensionDependency",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/GuidelineReference": "androidx/constraintlayout/core/widget/analyzer/GuidelineReference",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/HelperReferences": "androidx/constraintlayout/core/widget/analyzer/HelperReferences",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/HorizontalWidgetRun": "androidx/constraintlayout/core/widget/analyzer/HorizontalWidgetRun",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/RunGroup": "androidx/constraintlayout/core/widget/analyzer/RunGroup",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/VerticalWidgetRun": "androidx/constraintlayout/core/widget/analyzer/VerticalWidgetRun",
-      "androidx/car/ui/constraintlayout/core/widget/analyzer/WidgetRun": "androidx/constraintlayout/core/widget/analyzer/WidgetRun",
-      "androidx/car/ui/vectordrawable/graphics/drawable/AndroidResources": "androidx/vectordrawable/graphics/drawable/AndroidResources",
-      "androidx/car/ui/vectordrawable/graphics/drawable/Animatable2Compat": "androidx/vectordrawable/graphics/drawable/Animatable2Compat",
-      "androidx/car/ui/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat": "androidx/vectordrawable/graphics/drawable/AnimatedVectorDrawableCompat",
-      "androidx/car/ui/vectordrawable/graphics/drawable/AnimationUtilsCompat": "androidx/vectordrawable/graphics/drawable/AnimationUtilsCompat",
-      "androidx/car/ui/vectordrawable/graphics/drawable/AnimatorInflaterCompat": "androidx/vectordrawable/graphics/drawable/AnimatorInflaterCompat",
-      "androidx/car/ui/vectordrawable/graphics/drawable/ArgbEvaluator": "androidx/vectordrawable/graphics/drawable/ArgbEvaluator",
-      "androidx/car/ui/vectordrawable/graphics/drawable/PathInterpolatorCompat": "androidx/vectordrawable/graphics/drawable/PathInterpolatorCompat",
-      "androidx/car/ui/vectordrawable/graphics/drawable/VectorDrawableCommon": "androidx/vectordrawable/graphics/drawable/VectorDrawableCommon",
-      "androidx/car/ui/vectordrawable/graphics/drawable/VectorDrawableCompat": "androidx/vectordrawable/graphics/drawable/VectorDrawableCompat",
-      "androidx/car/ui/core/view/DragAndDropPermissionsCompat": "androidx/core/view/DragAndDropPermissionsCompat",
-      "androidx/car/ui/core/view/DragStartHelper": "androidx/core/view/DragStartHelper",
-      "androidx/car/ui/core/view/inputmethod/EditorInfoCompat": "androidx/core/view/inputmethod/EditorInfoCompat",
-      "androidx/car/ui/core/view/inputmethod/InputConnectionCompat": "androidx/core/view/inputmethod/InputConnectionCompat",
-      "androidx/car/ui/core/view/inputmethod/InputContentInfoCompat": "androidx/core/view/inputmethod/InputContentInfoCompat",
-      "androidx/car/ui/preference/EditTextPreferenceDialogFragment": "androidx/preference/EditTextPreferenceDialogFragment",
-      "androidx/car/ui/preference/ListPreferenceDialogFragment": "androidx/preference/ListPreferenceDialogFragment",
-      "androidx/car/ui/preference/MultiSelectListPreference": "androidx/preference/MultiSelectListPreference",
-      "androidx/car/ui/preference/MultiSelectListPreferenceDialogFragment": "androidx/preference/MultiSelectListPreferenceDialogFragment",
-      "androidx/car/ui/preference/PreferenceDialogFragment": "androidx/preference/PreferenceDialogFragment",
-      "androidx/car/ui/preference/PreferenceFragment": "androidx/preference/PreferenceFragment",
-      "androidx/car/ui/preference/SwitchPreference": "androidx/preference/SwitchPreference",
-      "androidx/car/ui/core/accessibilityservice/AccessibilityServiceInfoCompat": "androidx/core/accessibilityservice/AccessibilityServiceInfoCompat",
-      "androidx/car/ui/core/app/ActivityCompat": "androidx/core/app/ActivityCompat",
-      "androidx/car/ui/core/app/ActivityManagerCompat": "androidx/core/app/ActivityManagerCompat",
-      "androidx/car/ui/core/app/ActivityOptionsCompat": "androidx/core/app/ActivityOptionsCompat",
-      "androidx/car/ui/core/app/AlarmManagerCompat": "androidx/core/app/AlarmManagerCompat",
-      "androidx/car/ui/core/app/AppComponentFactory": "androidx/core/app/AppComponentFactory",
-      "androidx/car/ui/core/app/AppLaunchChecker": "androidx/core/app/AppLaunchChecker",
-      "androidx/car/ui/core/app/AppOpsManagerCompat": "androidx/core/app/AppOpsManagerCompat",
-      "androidx/car/ui/fragment/app/BackStackRecord": "androidx/fragment/app/BackStackRecord",
-      "androidx/car/ui/fragment/app/BackStackState": "androidx/fragment/app/BackStackState",
-      "androidx/car/ui/core/app/BundleCompat": "androidx/core/app/BundleCompat",
-      "androidx/car/ui/core/app/CoreComponentFactory": "androidx/core/app/CoreComponentFactory",
-      "androidx/car/ui/fragment/app/DialogFragment": "androidx/fragment/app/DialogFragment",
-      "androidx/car/ui/fragment/app/Fragment": "androidx/fragment/app/Fragment",
-      "androidx/car/ui/fragment/app/FragmentActivity": "androidx/fragment/app/FragmentActivity",
-      "androidx/car/ui/fragment/app/FragmentContainer": "androidx/fragment/app/FragmentContainer",
-      "androidx/car/ui/fragment/app/FragmentController": "androidx/fragment/app/FragmentController",
-      "androidx/car/ui/fragment/app/FragmentHostCallback": "androidx/fragment/app/FragmentHostCallback",
-      "androidx/car/ui/fragment/app/FragmentManager": "androidx/fragment/app/FragmentManager",
-      "androidx/car/ui/fragment/app/FragmentManagerImpl": "androidx/fragment/app/FragmentManagerImpl",
-      "androidx/car/ui/fragment/app/FragmentManagerNonConfig": "androidx/fragment/app/FragmentManagerNonConfig",
-      "androidx/car/ui/fragment/app/FragmentManagerState": "androidx/fragment/app/FragmentManagerState",
-      "androidx/car/ui/fragment/app/FragmentPagerAdapter": "androidx/fragment/app/FragmentPagerAdapter",
-      "androidx/car/ui/fragment/app/FragmentState": "androidx/fragment/app/FragmentState",
-      "androidx/car/ui/fragment/app/FragmentStatePagerAdapter": "androidx/fragment/app/FragmentStatePagerAdapter",
-      "androidx/car/ui/fragment/app/FragmentTabHost": "androidx/fragment/app/FragmentTabHost",
-      "androidx/car/ui/fragment/app/FragmentTransaction": "androidx/fragment/app/FragmentTransaction",
-      "androidx/car/ui/fragment/app/FragmentTransition": "androidx/fragment/app/FragmentTransition",
-      "androidx/car/ui/fragment/app/FragmentTransitionCompat21": "androidx/fragment/app/FragmentTransitionCompat21",
-      "androidx/car/ui/fragment/app/FragmentTransitionImpl": "androidx/fragment/app/FragmentTransitionImpl",
-      "androidx/car/ui/core/app/FrameMetricsAggregator": "androidx/core/app/FrameMetricsAggregator",
-      "androidx/car/ui/core/app/INotificationSideChannel": "androidx/core/app/INotificationSideChannel",
-      "androidx/car/ui/core/app/JobIntentService": "androidx/core/app/JobIntentService",
-      "androidx/car/ui/fragment/app/ListFragment": "androidx/fragment/app/ListFragment",
-      "androidx/car/ui/app/LoaderManager": "androidx/loader/app/LoaderManager",
-      "androidx/car/ui/app/LoaderManagerImpl": "androidx/loader/app/LoaderManagerImpl",
-      "androidx/car/ui/core/app/NavUtils": "androidx/core/app/NavUtils",
-      "androidx/car/ui/core/app/NotificationBuilderWithBuilderAccessor": "androidx/core/app/NotificationBuilderWithBuilderAccessor",
-      "androidx/car/ui/core/app/NotificationCompat": "androidx/core/app/NotificationCompat",
-      "androidx/car/ui/core/app/NotificationCompatBuilder": "androidx/core/app/NotificationCompatBuilder",
-      "androidx/car/ui/core/app/NotificationCompatExtras": "androidx/core/app/NotificationCompatExtras",
-      "androidx/car/ui/core/app/NotificationCompatJellybean": "androidx/core/app/NotificationCompatJellybean",
-      "androidx/car/ui/core/app/NotificationCompatSideChannelService": "androidx/core/app/NotificationCompatSideChannelService",
-      "androidx/car/ui/core/app/NotificationManagerCompat": "androidx/core/app/NotificationManagerCompat",
-      "androidx/car/ui/fragment/app/OneShotPreDrawListener": "androidx/fragment/app/OneShotPreDrawListener",
-      "androidx/car/ui/core/app/Person": "androidx/core/app/Person",
-      "androidx/car/ui/core/app/RemoteInput": "androidx/core/app/RemoteInput",
-      "androidx/car/ui/core/app/ServiceCompat": "androidx/core/app/ServiceCompat",
-      "androidx/car/ui/core/app/ShareCompat": "androidx/core/app/ShareCompat",
-      "androidx/car/ui/core/app/SharedElementCallback": "androidx/core/app/SharedElementCallback",
-      "androidx/car/ui/fragment/app/SuperNotCalledException": "androidx/fragment/app/SuperNotCalledException",
-      "androidx/car/ui/fragment/app/DefaultSpecialEffectsController": "androidx/fragment/app/DefaultSpecialEffectsController",
-      "androidx/car/ui/fragment/app/LogWriter": "androidx/fragment/app/LogWriter",
-      "androidx/car/ui/fragment/app/SpecialEffectsController": "androidx/fragment/app/SpecialEffectsController",
-      "androidx/car/ui/fragment/app/SpecialEffectsControllerFactory": "androidx/fragment/app/SpecialEffectsControllerFactory",
-      "androidx/car/ui/fragment/R": "androidx/fragment/R",
-      "androidx/car/ui/core/app/ComponentActivity": "androidx/core/app/ComponentActivity",
-      "androidx/car/ui/core/app/TaskStackBuilder": "androidx/core/app/TaskStackBuilder",
-      "androidx/car/ui/content/AsyncTaskLoader": "androidx/loader/content/AsyncTaskLoader",
-      "androidx/car/ui/core/content/ContentResolverCompat": "androidx/core/content/ContentResolverCompat",
-      "androidx/car/ui/core/content/ContextCompat": "androidx/core/content/ContextCompat",
-      "androidx/car/ui/content/CursorLoader": "androidx/loader/content/CursorLoader",
-      "androidx/car/ui/core/content/FileProvider": "androidx/core/content/FileProvider",
-      "androidx/car/ui/core/content/IntentCompat": "androidx/core/content/IntentCompat",
-      "androidx/car/ui/content/Loader": "androidx/loader/content/Loader",
-      "androidx/car/ui/core/content/MimeTypeFilter": "androidx/core/content/MimeTypeFilter",
-      "androidx/car/ui/content/ModernAsyncTask": "androidx/loader/content/ModernAsyncTask",
-      "androidx/car/ui/core/content/PermissionChecker": "androidx/core/content/PermissionChecker",
-      "androidx/car/ui/core/content/SharedPreferencesCompat": "androidx/core/content/SharedPreferencesCompat",
-      "androidx/car/ui/core/content/pm/ActivityInfoCompat": "androidx/core/content/pm/ActivityInfoCompat",
-      "androidx/car/ui/core/content/pm/PackageInfoCompat": "androidx/core/content/pm/PackageInfoCompat",
-      "androidx/car/ui/core/content/pm/PermissionInfoCompat": "androidx/core/content/pm/PermissionInfoCompat",
-      "androidx/car/ui/core/content/pm/ShortcutInfoCompat": "androidx/core/content/pm/ShortcutInfoCompat",
-      "androidx/car/ui/core/content/pm/ShortcutManagerCompat": "androidx/core/content/pm/ShortcutManagerCompat",
-      "androidx/car/ui/core/content/res/ColorStateListInflaterCompat": "androidx/core/content/res/ColorStateListInflaterCompat",
-      "androidx/car/ui/core/content/res/ComplexColorCompat": "androidx/core/content/res/ComplexColorCompat",
-      "androidx/car/ui/core/content/res/ConfigurationHelper": "androidx/core/content/res/ConfigurationHelper",
-      "androidx/car/ui/core/content/res/FontResourcesParserCompat": "androidx/core/content/res/FontResourcesParserCompat",
-      "androidx/car/ui/core/content/res/GradientColorInflaterCompat": "androidx/core/content/res/GradientColorInflaterCompat",
-      "androidx/car/ui/core/content/res/GrowingArrayUtils": "androidx/core/content/res/GrowingArrayUtils",
-      "androidx/car/ui/core/content/res/ResourcesCompat": "androidx/core/content/res/ResourcesCompat",
-      "androidx/car/ui/core/content/res/TypedArrayUtils": "androidx/core/content/res/TypedArrayUtils",
-      "androidx/car/ui/core/database/CursorWindowCompat": "androidx/core/database/CursorWindowCompat",
-      "androidx/car/ui/core/database/DatabaseUtilsCompat": "androidx/core/database/DatabaseUtilsCompat",
-      "androidx/car/ui/core/database/sqlite/SQLiteCursorCompat": "androidx/core/database/sqlite/SQLiteCursorCompat",
-      "androidx/car/ui/core/graphics/BitmapCompat": "androidx/core/graphics/BitmapCompat",
-      "androidx/car/ui/core/graphics/ColorUtils": "androidx/core/graphics/ColorUtils",
-      "androidx/car/ui/core/graphics/PaintCompat": "androidx/core/graphics/PaintCompat",
-      "androidx/car/ui/core/graphics/PathParser": "androidx/core/graphics/PathParser",
-      "androidx/car/ui/core/graphics/PathSegment": "androidx/core/graphics/PathSegment",
-      "androidx/car/ui/core/graphics/PathUtils": "androidx/core/graphics/PathUtils",
-      "androidx/car/ui/core/graphics/TypefaceCompat": "androidx/core/graphics/TypefaceCompat",
-      "androidx/car/ui/core/graphics/TypefaceCompatApi21Impl": "androidx/core/graphics/TypefaceCompatApi21Impl",
-      "androidx/car/ui/core/graphics/TypefaceCompatApi24Impl": "androidx/core/graphics/TypefaceCompatApi24Impl",
-      "androidx/car/ui/core/graphics/TypefaceCompatApi26Impl": "androidx/core/graphics/TypefaceCompatApi26Impl",
-      "androidx/car/ui/core/graphics/TypefaceCompatApi28Impl": "androidx/core/graphics/TypefaceCompatApi28Impl",
-      "androidx/car/ui/core/graphics/TypefaceCompatBaseImpl": "androidx/core/graphics/TypefaceCompatBaseImpl",
-      "androidx/car/ui/core/graphics/TypefaceCompatUtil": "androidx/core/graphics/TypefaceCompatUtil",
-      "androidx/car/ui/core/graphics/drawable/DrawableCompat": "androidx/core/graphics/drawable/DrawableCompat",
-      "androidx/car/ui/core/graphics/drawable/IconCompat": "androidx/core/graphics/drawable/IconCompat",
-      "androidx/car/ui/core/graphics/drawable/IconCompatParcelizer": "androidx/core/graphics/drawable/IconCompatParcelizer",
-      "androidx/car/ui/core/app/RemoteActionCompatParcelizer": "androidx/core/app/RemoteActionCompatParcelizer",
-      "androidx/car/ui/core/graphics/drawable/RoundedBitmapDrawable": "androidx/core/graphics/drawable/RoundedBitmapDrawable",
-      "androidx/car/ui/core/graphics/drawable/RoundedBitmapDrawable21": "androidx/core/graphics/drawable/RoundedBitmapDrawable21",
-      "androidx/car/ui/core/graphics/drawable/RoundedBitmapDrawableFactory": "androidx/core/graphics/drawable/RoundedBitmapDrawableFactory",
-      "androidx/car/ui/core/graphics/drawable/TintAwareDrawable": "androidx/core/graphics/drawable/TintAwareDrawable",
-      "androidx/car/ui/core/graphics/drawable/WrappedDrawable": "androidx/core/graphics/drawable/WrappedDrawable",
-      "androidx/car/ui/core/graphics/drawable/WrappedDrawableApi14": "androidx/core/graphics/drawable/WrappedDrawableApi14",
-      "androidx/car/ui/core/graphics/drawable/WrappedDrawableApi21": "androidx/core/graphics/drawable/WrappedDrawableApi21",
-      "androidx/car/ui/core/hardware/display/DisplayManagerCompat": "androidx/core/hardware/display/DisplayManagerCompat",
-      "androidx/car/ui/core/hardware/fingerprint/FingerprintManagerCompat": "androidx/core/hardware/fingerprint/FingerprintManagerCompat",
-      "androidx/car/ui/core/internal/view/SupportMenu": "androidx/core/internal/view/SupportMenu",
-      "androidx/car/ui/core/internal/view/SupportMenuItem": "androidx/core/internal/view/SupportMenuItem",
-      "androidx/car/ui/core/internal/view/SupportSubMenu": "androidx/core/internal/view/SupportSubMenu",
-      "androidx/car/ui/core/math/MathUtils": "androidx/core/math/MathUtils",
-      "androidx/car/ui/core/net/ConnectivityManagerCompat": "androidx/core/net/ConnectivityManagerCompat",
-      "androidx/car/ui/core/net/DatagramSocketWrapper": "androidx/core/net/DatagramSocketWrapper",
-      "androidx/car/ui/core/net/TrafficStatsCompat": "androidx/core/net/TrafficStatsCompat",
-      "androidx/car/ui/core/os/BuildCompat": "androidx/core/os/BuildCompat",
-      "androidx/car/ui/core/os/CancellationSignal": "androidx/core/os/CancellationSignal",
-      "androidx/car/ui/core/os/ConfigurationCompat": "androidx/core/os/ConfigurationCompat",
-      "androidx/car/ui/core/os/EnvironmentCompat": "androidx/core/os/EnvironmentCompat",
-      "androidx/car/ui/core/os/HandlerCompat": "androidx/core/os/HandlerCompat",
-      "androidx/car/ui/core/os/IResultReceiver": "androidx/core/os/IResultReceiver",
-      "androidx/car/ui/core/os/LocaleHelper": "androidx/core/os/LocaleHelper",
-      "androidx/car/ui/core/os/LocaleListCompat": "androidx/core/os/LocaleListCompat",
-      "androidx/car/ui/core/os/LocaleListHelper": "androidx/core/os/LocaleListHelper",
-      "androidx/car/ui/core/os/LocaleListInterface": "androidx/core/os/LocaleListInterface",
-      "androidx/car/ui/core/os/OperationCanceledException": "androidx/core/os/OperationCanceledException",
-      "androidx/car/ui/core/os/ParcelCompat": "androidx/core/os/ParcelCompat",
-      "androidx/car/ui/core/os/ParcelableCompat": "androidx/core/os/ParcelableCompat",
-      "androidx/car/ui/core/os/ParcelableCompatCreatorCallbacks": "androidx/core/os/ParcelableCompatCreatorCallbacks",
-      "androidx/car/ui/core/os/TraceCompat": "androidx/core/os/TraceCompat",
-      "androidx/car/ui/core/os/UserManagerCompat": "androidx/core/os/UserManagerCompat",
-      "androidx/car/ui/core/provider/FontRequest": "androidx/core/provider/FontRequest",
-      "androidx/car/ui/core/provider/FontsContractCompat": "androidx/core/provider/FontsContractCompat",
-      "androidx/car/ui/core/provider/SelfDestructiveThread": "androidx/core/provider/SelfDestructiveThread",
-      "androidx/car/ui/core/text/BidiFormatter": "androidx/core/text/BidiFormatter",
-      "androidx/car/ui/core/text/HtmlCompat": "androidx/core/text/HtmlCompat",
-      "androidx/car/ui/core/text/ICUCompat": "androidx/core/text/ICUCompat",
-      "androidx/car/ui/core/text/PrecomputedTextCompat": "androidx/core/text/PrecomputedTextCompat",
-      "androidx/car/ui/core/text/TextDirectionHeuristicCompat": "androidx/core/text/TextDirectionHeuristicCompat",
-      "androidx/car/ui/core/text/TextDirectionHeuristicsCompat": "androidx/core/text/TextDirectionHeuristicsCompat",
-      "androidx/car/ui/core/text/TextUtilsCompat": "androidx/core/text/TextUtilsCompat",
-      "androidx/car/ui/core/text/util/FindAddress": "androidx/core/text/util/FindAddress",
-      "androidx/car/ui/core/text/util/LinkifyCompat": "androidx/core/text/util/LinkifyCompat",
-      "androidx/car/ui/collection/ArrayMap": "androidx/collection/ArrayMap",
-      "androidx/car/ui/collection/ArraySet": "androidx/collection/ArraySet",
-      "androidx/car/ui/core/util/AtomicFile": "androidx/core/util/AtomicFile",
-      "androidx/car/ui/collection/CircularArray": "androidx/collection/CircularArray",
-      "androidx/car/ui/collection/CircularIntArray": "androidx/collection/CircularIntArray",
-      "androidx/car/ui/core/util/Consumer": "androidx/core/util/Consumer",
-      "androidx/car/ui/collection/ContainerHelpers": "androidx/collection/ContainerHelpers",
-      "androidx/car/ui/core/util/DebugUtils": "androidx/core/util/DebugUtils",
-      "androidx/car/ui/core/util/LogWriter": "androidx/core/util/LogWriter",
-      "androidx/car/ui/collection/LongSparseArray": "androidx/collection/LongSparseArray",
-      "androidx/car/ui/collection/LruCache": "androidx/collection/LruCache",
-      "androidx/car/ui/collection/MapCollections": "androidx/collection/MapCollections",
-      "androidx/car/ui/core/util/ObjectsCompat": "androidx/core/util/ObjectsCompat",
-      "androidx/car/ui/core/util/Pair": "androidx/core/util/Pair",
-      "androidx/car/ui/core/util/PatternsCompat": "androidx/core/util/PatternsCompat",
-      "androidx/car/ui/core/util/Pools": "androidx/core/util/Pools",
-      "androidx/car/ui/core/util/Preconditions": "androidx/core/util/Preconditions",
-      "androidx/car/ui/collection/SimpleArrayMap": "androidx/collection/SimpleArrayMap",
-      "androidx/car/ui/collection/SparseArrayCompat": "androidx/collection/SparseArrayCompat",
-      "androidx/car/ui/collection/IndexBasedArrayIterator": "androidx/collection/IndexBasedArrayIterator",
-      "androidx/car/ui/core/util/TimeUtils": "androidx/core/util/TimeUtils",
-      "androidx/car/ui/customview/view/AbsSavedState": "androidx/customview/view/AbsSavedState",
-      "androidx/car/ui/core/view/AccessibilityDelegateCompat": "androidx/core/view/AccessibilityDelegateCompat",
-      "androidx/car/ui/core/view/ActionProvider": "androidx/core/view/ActionProvider",
-      "androidx/car/ui/asynclayoutinflater/view/AsyncLayoutInflater": "androidx/asynclayoutinflater/view/AsyncLayoutInflater",
-      "androidx/car/ui/core/view/DisplayCutoutCompat": "androidx/core/view/DisplayCutoutCompat",
-      "androidx/car/ui/core/view/GestureDetectorCompat": "androidx/core/view/GestureDetectorCompat",
-      "androidx/car/ui/core/view/GravityCompat": "androidx/core/view/GravityCompat",
-      "androidx/car/ui/core/view/InputDeviceCompat": "androidx/core/view/InputDeviceCompat",
-      "androidx/car/ui/core/view/KeyEventDispatcher": "androidx/core/view/KeyEventDispatcher",
-      "androidx/car/ui/core/view/LayoutInflaterCompat": "androidx/core/view/LayoutInflaterCompat",
-      "androidx/car/ui/core/view/LayoutInflaterFactory": "androidx/core/view/LayoutInflaterFactory",
-      "androidx/car/ui/core/view/MarginLayoutParamsCompat": "androidx/core/view/MarginLayoutParamsCompat",
-      "androidx/car/ui/core/view/MenuCompat": "androidx/core/view/MenuCompat",
-      "androidx/car/ui/core/view/MenuItemCompat": "androidx/core/view/MenuItemCompat",
-      "androidx/car/ui/core/view/MotionEventCompat": "androidx/core/view/MotionEventCompat",
-      "androidx/car/ui/core/view/NestedScrollingChild": "androidx/core/view/NestedScrollingChild",
-      "androidx/car/ui/core/view/NestedScrollingChild2": "androidx/core/view/NestedScrollingChild2",
-      "androidx/car/ui/core/view/NestedScrollingChildHelper": "androidx/core/view/NestedScrollingChildHelper",
-      "androidx/car/ui/core/view/NestedScrollingParent": "androidx/core/view/NestedScrollingParent",
-      "androidx/car/ui/core/view/NestedScrollingParent2": "androidx/core/view/NestedScrollingParent2",
-      "androidx/car/ui/core/view/NestedScrollingParentHelper": "androidx/core/view/NestedScrollingParentHelper",
-      "androidx/car/ui/core/view/OnApplyWindowInsetsListener": "androidx/core/view/OnApplyWindowInsetsListener",
-      "androidx/car/ui/core/view/PointerIconCompat": "androidx/core/view/PointerIconCompat",
-      "androidx/car/ui/core/view/ScaleGestureDetectorCompat": "androidx/core/view/ScaleGestureDetectorCompat",
-      "androidx/car/ui/core/view/ScrollingView": "androidx/core/view/ScrollingView",
-      "androidx/car/ui/core/view/TintableBackgroundView": "androidx/core/view/TintableBackgroundView",
-      "androidx/car/ui/core/view/VelocityTrackerCompat": "androidx/core/view/VelocityTrackerCompat",
-      "androidx/car/ui/core/view/ViewCompat": "androidx/core/view/ViewCompat",
-      "androidx/car/ui/core/view/ViewConfigurationCompat": "androidx/core/view/ViewConfigurationCompat",
-      "androidx/car/ui/core/view/ViewGroupCompat": "androidx/core/view/ViewGroupCompat",
-      "androidx/car/ui/core/view/ViewParentCompat": "androidx/core/view/ViewParentCompat",
-      "androidx/car/ui/core/view/ViewPropertyAnimatorCompat": "androidx/core/view/ViewPropertyAnimatorCompat",
-      "androidx/car/ui/core/view/ViewPropertyAnimatorListener": "androidx/core/view/ViewPropertyAnimatorListener",
-      "androidx/car/ui/core/view/ViewPropertyAnimatorListenerAdapter": "androidx/core/view/ViewPropertyAnimatorListenerAdapter",
-      "androidx/car/ui/core/view/ViewPropertyAnimatorUpdateListener": "androidx/core/view/ViewPropertyAnimatorUpdateListener",
-      "androidx/car/ui/core/view/WindowCompat": "androidx/core/view/WindowCompat",
-      "androidx/car/ui/core/view/WindowInsetsCompat": "androidx/core/view/WindowInsetsCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityEventCompat": "androidx/core/view/accessibility/AccessibilityEventCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityManagerCompat": "androidx/core/view/accessibility/AccessibilityManagerCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityNodeInfoCompat": "androidx/core/view/accessibility/AccessibilityNodeInfoCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityNodeProviderCompat": "androidx/core/view/accessibility/AccessibilityNodeProviderCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityRecordCompat": "androidx/core/view/accessibility/AccessibilityRecordCompat",
-      "androidx/car/ui/core/view/accessibility/AccessibilityWindowInfoCompat": "androidx/core/view/accessibility/AccessibilityWindowInfoCompat",
-      "androidx/car/ui/core/view/animation/PathInterpolatorApi14": "androidx/core/view/animation/PathInterpolatorApi14",
-      "androidx/car/ui/core/view/animation/PathInterpolatorCompat": "androidx/core/view/animation/PathInterpolatorCompat",
-      "androidx/car/ui/core/widget/AutoScrollHelper": "androidx/core/widget/AutoScrollHelper",
-      "androidx/car/ui/core/widget/AutoSizeableTextView": "androidx/core/widget/AutoSizeableTextView",
-      "androidx/car/ui/core/widget/CompoundButtonCompat": "androidx/core/widget/CompoundButtonCompat",
-      "androidx/car/ui/core/widget/ContentLoadingProgressBar": "androidx/core/widget/ContentLoadingProgressBar",
-      "androidx/car/ui/drawerlayout/widget/DrawerLayout": "androidx/drawerlayout/widget/DrawerLayout",
-      "androidx/car/ui/core/widget/EdgeEffectCompat": "androidx/core/widget/EdgeEffectCompat",
-      "androidx/car/ui/customview/widget/ExploreByTouchHelper": "androidx/customview/widget/ExploreByTouchHelper",
-      "androidx/car/ui/customview/widget/FocusStrategy": "androidx/customview/widget/FocusStrategy",
-      "androidx/car/ui/core/widget/ImageViewCompat": "androidx/core/widget/ImageViewCompat",
-      "androidx/car/ui/core/widget/ListPopupWindowCompat": "androidx/core/widget/ListPopupWindowCompat",
-      "androidx/car/ui/core/widget/ListViewAutoScrollHelper": "androidx/core/widget/ListViewAutoScrollHelper",
-      "androidx/car/ui/core/widget/ListViewCompat": "androidx/core/widget/ListViewCompat",
-      "androidx/car/ui/core/widget/NestedScrollView": "androidx/core/widget/NestedScrollView",
-      "androidx/car/ui/core/widget/PopupMenuCompat": "androidx/core/widget/PopupMenuCompat",
-      "androidx/car/ui/core/widget/PopupWindowCompat": "androidx/core/widget/PopupWindowCompat",
-      "androidx/car/ui/core/widget/ScrollerCompat": "androidx/core/widget/ScrollerCompat",
-      "androidx/car/ui/core/widget/TextViewCompat": "androidx/core/widget/TextViewCompat",
-      "androidx/car/ui/core/widget/TintableCompoundButton": "androidx/core/widget/TintableCompoundButton",
-      "androidx/car/ui/core/widget/TintableImageSourceView": "androidx/core/widget/TintableImageSourceView",
-      "androidx/car/ui/customview/widget/ViewDragHelper": "androidx/customview/widget/ViewDragHelper",
-      "androidx/car/ui/customview/widget/Openable": "androidx/customview/widget/Openable",
-      "androidx/car/ui/appcompat/app/ActionBar": "androidx/appcompat/app/ActionBar",
-      "androidx/car/ui/appcompat/app/ActionBarDrawerToggle": "androidx/appcompat/app/ActionBarDrawerToggle",
-      "androidx/car/ui/appcompat/app/ActionBarDrawerToggleHoneycomb": "androidx/appcompat/app/ActionBarDrawerToggleHoneycomb",
-      "androidx/car/ui/appcompat/app/AlertController": "androidx/appcompat/app/AlertController",
-      "androidx/car/ui/appcompat/app/AlertDialog": "androidx/appcompat/app/AlertDialog",
-      "androidx/car/ui/appcompat/app/AppCompatActivity": "androidx/appcompat/app/AppCompatActivity",
-      "androidx/car/ui/appcompat/app/AppCompatCallback": "androidx/appcompat/app/AppCompatCallback",
-      "androidx/car/ui/appcompat/app/AppCompatDelegate": "androidx/appcompat/app/AppCompatDelegate",
-      "androidx/car/ui/appcompat/app/AppCompatDelegateImpl": "androidx/appcompat/app/AppCompatDelegateImpl",
-      "androidx/car/ui/appcompat/app/AppCompatDialog": "androidx/appcompat/app/AppCompatDialog",
-      "androidx/car/ui/appcompat/app/AppCompatDialogFragment": "androidx/appcompat/app/AppCompatDialogFragment",
-      "androidx/car/ui/appcompat/app/AppCompatViewInflater": "androidx/appcompat/app/AppCompatViewInflater",
-      "androidx/car/ui/appcompat/app/NavItemSelectedListener": "androidx/appcompat/app/NavItemSelectedListener",
-      "androidx/car/ui/appcompat/app/ResourcesFlusher": "androidx/appcompat/app/ResourcesFlusher",
-      "androidx/car/ui/appcompat/app/ToolbarActionBar": "androidx/appcompat/app/ToolbarActionBar",
-      "androidx/car/ui/appcompat/app/TwilightCalculator": "androidx/appcompat/app/TwilightCalculator",
-      "androidx/car/ui/appcompat/app/TwilightManager": "androidx/appcompat/app/TwilightManager",
-      "androidx/car/ui/appcompat/app/WindowDecorActionBar": "androidx/appcompat/app/WindowDecorActionBar",
-      "androidx/car/ui/appcompat/R": "androidx/appcompat/R",
-      "androidx/car/ui/appcompat/content/res/AppCompatResources": "androidx/appcompat/content/res/AppCompatResources",
-      "androidx/car/ui/appcompat/graphics/drawable/AnimatedStateListDrawableCompat": "androidx/appcompat/graphics/drawable/AnimatedStateListDrawableCompat",
-      "androidx/car/ui/appcompat/graphics/drawable/DrawableContainer": "androidx/appcompat/graphics/drawable/DrawableContainer",
-      "androidx/car/ui/appcompat/graphics/drawable/DrawableWrapper": "androidx/appcompat/graphics/drawable/DrawableWrapper",
-      "androidx/car/ui/appcompat/graphics/drawable/DrawerArrowDrawable": "androidx/appcompat/graphics/drawable/DrawerArrowDrawable",
-      "androidx/car/ui/appcompat/graphics/drawable/StateListDrawable": "androidx/appcompat/graphics/drawable/StateListDrawable",
-      "androidx/car/ui/preference/internal/PreferenceImageView": "androidx/preference/internal/PreferenceImageView",
-      "androidx/car/ui/preference/AndroidResources": "androidx/preference/AndroidResources",
-      "androidx/car/ui/preference/CheckBoxPreference": "androidx/preference/CheckBoxPreference",
-      "androidx/car/ui/preference/CollapsiblePreferenceGroupController": "androidx/preference/CollapsiblePreferenceGroupController",
-      "androidx/car/ui/preference/DialogPreference": "androidx/preference/DialogPreference",
-      "androidx/car/ui/preference/DropDownPreference": "androidx/preference/DropDownPreference",
-      "androidx/car/ui/preference/EditTextPreference": "androidx/preference/EditTextPreference",
-      "androidx/car/ui/preference/EditTextPreferenceDialogFragmentCompat": "androidx/preference/EditTextPreferenceDialogFragmentCompat",
-      "androidx/car/ui/preference/ListPreference": "androidx/preference/ListPreference",
-      "androidx/car/ui/preference/ListPreferenceDialogFragmentCompat": "androidx/preference/ListPreferenceDialogFragmentCompat",
-      "androidx/car/ui/preference/MultiSelectListPreferenceDialogFragmentCompat": "androidx/preference/MultiSelectListPreferenceDialogFragmentCompat",
-      "androidx/car/ui/preference/Preference": "androidx/preference/Preference",
-      "androidx/car/ui/preference/PreferenceCategory": "androidx/preference/PreferenceCategory",
-      "androidx/car/ui/preference/PreferenceDataStore": "androidx/preference/PreferenceDataStore",
-      "androidx/car/ui/preference/PreferenceDialogFragmentCompat": "androidx/preference/PreferenceDialogFragmentCompat",
-      "androidx/car/ui/preference/PreferenceFragmentCompat": "androidx/preference/PreferenceFragmentCompat",
-      "androidx/car/ui/preference/PreferenceGroup": "androidx/preference/PreferenceGroup",
-      "androidx/car/ui/preference/PreferenceGroupAdapter": "androidx/preference/PreferenceGroupAdapter",
-      "androidx/car/ui/preference/PreferenceInflater": "androidx/preference/PreferenceInflater",
-      "androidx/car/ui/preference/PreferenceManager": "androidx/preference/PreferenceManager",
-      "androidx/car/ui/preference/PreferenceRecyclerViewAccessibilityDelegate": "androidx/preference/PreferenceRecyclerViewAccessibilityDelegate",
-      "androidx/car/ui/preference/PreferenceScreen": "androidx/preference/PreferenceScreen",
-      "androidx/car/ui/preference/PreferenceViewHolder": "androidx/preference/PreferenceViewHolder",
-      "androidx/car/ui/preference/R": "androidx/preference/R",
-      "androidx/car/ui/preference/SeekBarPreference": "androidx/preference/SeekBarPreference",
-      "androidx/car/ui/preference/SwitchPreferenceCompat": "androidx/preference/SwitchPreferenceCompat",
-      "androidx/car/ui/preference/TwoStatePreference": "androidx/preference/TwoStatePreference",
-      "androidx/car/ui/preference/UnPressableLinearLayout": "androidx/preference/UnPressableLinearLayout",
-      "androidx/car/ui/preference/internal/AbstractMultiSelectListPreference": "androidx/preference/internal/AbstractMultiSelectListPreference",
-      "androidx/car/ui/preference/ExpandButton": "androidx/preference/ExpandButton",
-      "androidx/car/ui/recyclerview/R": "androidx/recyclerview/R",
-      "androidx/car/ui/recyclerview/widget/AsyncDifferConfig": "androidx/recyclerview/widget/AsyncDifferConfig",
-      "androidx/car/ui/recyclerview/widget/AsyncListDiffer": "androidx/recyclerview/widget/AsyncListDiffer",
-      "androidx/car/ui/recyclerview/widget/ListAdapter": "androidx/recyclerview/widget/ListAdapter",
-      "androidx/car/ui/appcompat/text/AllCapsTransformationMethod": "androidx/appcompat/text/AllCapsTransformationMethod",
-      "androidx/car/ui/recyclerview/widget/AdapterListUpdateCallback": "androidx/recyclerview/widget/AdapterListUpdateCallback",
-      "androidx/car/ui/recyclerview/widget/AsyncListUtil": "androidx/recyclerview/widget/AsyncListUtil",
-      "androidx/car/ui/recyclerview/widget/BatchingListUpdateCallback": "androidx/recyclerview/widget/BatchingListUpdateCallback",
-      "androidx/car/ui/recyclerview/widget/DiffUtil": "androidx/recyclerview/widget/DiffUtil",
-      "androidx/car/ui/recyclerview/widget/ListUpdateCallback": "androidx/recyclerview/widget/ListUpdateCallback",
-      "androidx/car/ui/recyclerview/widget/MessageThreadUtil": "androidx/recyclerview/widget/MessageThreadUtil",
-      "androidx/car/ui/recyclerview/widget/SortedList": "androidx/recyclerview/widget/SortedList",
-      "androidx/car/ui/recyclerview/widget/ThreadUtil": "androidx/recyclerview/widget/ThreadUtil",
-      "androidx/car/ui/recyclerview/widget/TileList": "androidx/recyclerview/widget/TileList",
-      "androidx/car/ui/appcompat/view/ActionBarPolicy": "androidx/appcompat/view/ActionBarPolicy",
-      "androidx/car/ui/appcompat/view/ActionMode": "androidx/appcompat/view/ActionMode",
-      "androidx/car/ui/appcompat/view/CollapsibleActionView": "androidx/appcompat/view/CollapsibleActionView",
-      "androidx/car/ui/appcompat/view/ContextThemeWrapper": "androidx/appcompat/view/ContextThemeWrapper",
-      "androidx/car/ui/appcompat/view/StandaloneActionMode": "androidx/appcompat/view/StandaloneActionMode",
-      "androidx/car/ui/appcompat/view/SupportActionModeWrapper": "androidx/appcompat/view/SupportActionModeWrapper",
-      "androidx/car/ui/appcompat/view/SupportMenuInflater": "androidx/appcompat/view/SupportMenuInflater",
-      "androidx/car/ui/appcompat/view/ViewPropertyAnimatorCompatSet": "androidx/appcompat/view/ViewPropertyAnimatorCompatSet",
-      "androidx/car/ui/appcompat/view/WindowCallbackWrapper": "androidx/appcompat/view/WindowCallbackWrapper",
-      "androidx/car/ui/appcompat/view/menu/ActionMenuItem": "androidx/appcompat/view/menu/ActionMenuItem",
-      "androidx/car/ui/appcompat/view/menu/ActionMenuItemView": "androidx/appcompat/view/menu/ActionMenuItemView",
-      "androidx/car/ui/appcompat/view/menu/BaseMenuPresenter": "androidx/appcompat/view/menu/BaseMenuPresenter",
-      "androidx/car/ui/appcompat/view/menu/BaseMenuWrapper": "androidx/appcompat/view/menu/BaseMenuWrapper",
-      "androidx/car/ui/appcompat/view/menu/BaseWrapper": "androidx/appcompat/view/menu/BaseWrapper",
-      "androidx/car/ui/appcompat/view/menu/CascadingMenuPopup": "androidx/appcompat/view/menu/CascadingMenuPopup",
-      "androidx/car/ui/appcompat/view/menu/ExpandedMenuView": "androidx/appcompat/view/menu/ExpandedMenuView",
-      "androidx/car/ui/appcompat/view/menu/ListMenuItemView": "androidx/appcompat/view/menu/ListMenuItemView",
-      "androidx/car/ui/appcompat/view/menu/ListMenuPresenter": "androidx/appcompat/view/menu/ListMenuPresenter",
-      "androidx/car/ui/appcompat/view/menu/MenuAdapter": "androidx/appcompat/view/menu/MenuAdapter",
-      "androidx/car/ui/appcompat/view/menu/MenuBuilder": "androidx/appcompat/view/menu/MenuBuilder",
-      "androidx/car/ui/appcompat/view/menu/MenuDialogHelper": "androidx/appcompat/view/menu/MenuDialogHelper",
-      "androidx/car/ui/appcompat/view/menu/MenuHelper": "androidx/appcompat/view/menu/MenuHelper",
-      "androidx/car/ui/appcompat/view/menu/MenuItemImpl": "androidx/appcompat/view/menu/MenuItemImpl",
-      "androidx/car/ui/appcompat/view/menu/MenuItemWrapperICS": "androidx/appcompat/view/menu/MenuItemWrapperICS",
-      "androidx/car/ui/appcompat/view/menu/MenuItemWrapperJB": "androidx/appcompat/view/menu/MenuItemWrapperJB",
-      "androidx/car/ui/appcompat/view/menu/MenuPopup": "androidx/appcompat/view/menu/MenuPopup",
-      "androidx/car/ui/appcompat/view/menu/MenuPopupHelper": "androidx/appcompat/view/menu/MenuPopupHelper",
-      "androidx/car/ui/appcompat/view/menu/MenuPresenter": "androidx/appcompat/view/menu/MenuPresenter",
-      "androidx/car/ui/appcompat/view/menu/MenuView": "androidx/appcompat/view/menu/MenuView",
-      "androidx/car/ui/appcompat/view/menu/MenuWrapperFactory": "androidx/appcompat/view/menu/MenuWrapperFactory",
-      "androidx/car/ui/appcompat/view/menu/MenuWrapperICS": "androidx/appcompat/view/menu/MenuWrapperICS",
-      "androidx/car/ui/appcompat/view/menu/ShowableListMenu": "androidx/appcompat/view/menu/ShowableListMenu",
-      "androidx/car/ui/appcompat/view/menu/StandardMenuPopup": "androidx/appcompat/view/menu/StandardMenuPopup",
-      "androidx/car/ui/appcompat/view/menu/SubMenuBuilder": "androidx/appcompat/view/menu/SubMenuBuilder",
-      "androidx/car/ui/appcompat/view/menu/SubMenuWrapperICS": "androidx/appcompat/view/menu/SubMenuWrapperICS",
-      "androidx/car/ui/appcompat/widget/AbsActionBarView": "androidx/appcompat/widget/AbsActionBarView",
-      "androidx/car/ui/appcompat/widget/ActionBarBackgroundDrawable": "androidx/appcompat/widget/ActionBarBackgroundDrawable",
-      "androidx/car/ui/appcompat/widget/ActionBarContainer": "androidx/appcompat/widget/ActionBarContainer",
-      "androidx/car/ui/appcompat/widget/ActionBarContextView": "androidx/appcompat/widget/ActionBarContextView",
-      "androidx/car/ui/appcompat/widget/ActionBarOverlayLayout": "androidx/appcompat/widget/ActionBarOverlayLayout",
-      "androidx/car/ui/appcompat/widget/ActionMenuPresenter": "androidx/appcompat/widget/ActionMenuPresenter",
-      "androidx/car/ui/appcompat/widget/ActionMenuView": "androidx/appcompat/widget/ActionMenuView",
-      "androidx/car/ui/appcompat/widget/ActivityChooserModel": "androidx/appcompat/widget/ActivityChooserModel",
-      "androidx/car/ui/appcompat/widget/ActivityChooserView": "androidx/appcompat/widget/ActivityChooserView",
-      "androidx/car/ui/recyclerview/widget/AdapterHelper": "androidx/recyclerview/widget/AdapterHelper",
-      "androidx/car/ui/appcompat/widget/AlertDialogLayout": "androidx/appcompat/widget/AlertDialogLayout",
-      "androidx/car/ui/appcompat/widget/AppCompatAutoCompleteTextView": "androidx/appcompat/widget/AppCompatAutoCompleteTextView",
-      "androidx/car/ui/appcompat/widget/AppCompatBackgroundHelper": "androidx/appcompat/widget/AppCompatBackgroundHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatButton": "androidx/appcompat/widget/AppCompatButton",
-      "androidx/car/ui/appcompat/widget/AppCompatCheckBox": "androidx/appcompat/widget/AppCompatCheckBox",
-      "androidx/car/ui/appcompat/widget/AppCompatCheckedTextView": "androidx/appcompat/widget/AppCompatCheckedTextView",
-      "androidx/car/ui/appcompat/widget/AppCompatCompoundButtonHelper": "androidx/appcompat/widget/AppCompatCompoundButtonHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatDrawableManager": "androidx/appcompat/widget/AppCompatDrawableManager",
-      "androidx/car/ui/appcompat/widget/AppCompatEditText": "androidx/appcompat/widget/AppCompatEditText",
-      "androidx/car/ui/appcompat/widget/AppCompatHintHelper": "androidx/appcompat/widget/AppCompatHintHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatImageButton": "androidx/appcompat/widget/AppCompatImageButton",
-      "androidx/car/ui/appcompat/widget/AppCompatImageHelper": "androidx/appcompat/widget/AppCompatImageHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatImageView": "androidx/appcompat/widget/AppCompatImageView",
-      "androidx/car/ui/appcompat/widget/AppCompatMultiAutoCompleteTextView": "androidx/appcompat/widget/AppCompatMultiAutoCompleteTextView",
-      "androidx/car/ui/appcompat/widget/AppCompatPopupWindow": "androidx/appcompat/widget/AppCompatPopupWindow",
-      "androidx/car/ui/appcompat/widget/AppCompatProgressBarHelper": "androidx/appcompat/widget/AppCompatProgressBarHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatRadioButton": "androidx/appcompat/widget/AppCompatRadioButton",
-      "androidx/car/ui/appcompat/widget/AppCompatRatingBar": "androidx/appcompat/widget/AppCompatRatingBar",
-      "androidx/car/ui/appcompat/widget/AppCompatSeekBar": "androidx/appcompat/widget/AppCompatSeekBar",
-      "androidx/car/ui/appcompat/widget/AppCompatSeekBarHelper": "androidx/appcompat/widget/AppCompatSeekBarHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatSpinner": "androidx/appcompat/widget/AppCompatSpinner",
-      "androidx/car/ui/appcompat/widget/AppCompatTextHelper": "androidx/appcompat/widget/AppCompatTextHelper",
-      "androidx/car/ui/appcompat/widget/AppCompatTextView": "androidx/appcompat/widget/AppCompatTextView",
-      "androidx/car/ui/appcompat/widget/AppCompatTextViewAutoSizeHelper": "androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper",
-      "androidx/car/ui/appcompat/widget/ButtonBarLayout": "androidx/appcompat/widget/ButtonBarLayout",
-      "androidx/car/ui/recyclerview/widget/ChildHelper": "androidx/recyclerview/widget/ChildHelper",
-      "androidx/car/ui/appcompat/widget/ContentFrameLayout": "androidx/appcompat/widget/ContentFrameLayout",
-      "androidx/car/ui/appcompat/widget/DecorContentParent": "androidx/appcompat/widget/DecorContentParent",
-      "androidx/car/ui/appcompat/widget/DecorToolbar": "androidx/appcompat/widget/DecorToolbar",
-      "androidx/car/ui/recyclerview/widget/DefaultItemAnimator": "androidx/recyclerview/widget/DefaultItemAnimator",
-      "androidx/car/ui/appcompat/widget/DialogTitle": "androidx/appcompat/widget/DialogTitle",
-      "androidx/car/ui/recyclerview/widget/DividerItemDecoration": "androidx/recyclerview/widget/DividerItemDecoration",
-      "androidx/car/ui/appcompat/widget/DrawableUtils": "androidx/appcompat/widget/DrawableUtils",
-      "androidx/car/ui/appcompat/widget/DropDownListView": "androidx/appcompat/widget/DropDownListView",
-      "androidx/car/ui/recyclerview/widget/FastScroller": "androidx/recyclerview/widget/FastScroller",
-      "androidx/car/ui/appcompat/widget/FitWindowsFrameLayout": "androidx/appcompat/widget/FitWindowsFrameLayout",
-      "androidx/car/ui/appcompat/widget/FitWindowsLinearLayout": "androidx/appcompat/widget/FitWindowsLinearLayout",
-      "androidx/car/ui/appcompat/widget/FitWindowsViewGroup": "androidx/appcompat/widget/FitWindowsViewGroup",
-      "androidx/car/ui/appcompat/widget/ForwardingListener": "androidx/appcompat/widget/ForwardingListener",
-      "androidx/car/ui/recyclerview/widget/GapWorker": "androidx/recyclerview/widget/GapWorker",
-      "androidx/car/ui/recyclerview/widget/GridLayoutManager": "androidx/recyclerview/widget/GridLayoutManager",
-      "androidx/car/ui/recyclerview/widget/LayoutState": "androidx/recyclerview/widget/LayoutState",
-      "androidx/car/ui/appcompat/widget/LinearLayoutCompat": "androidx/appcompat/widget/LinearLayoutCompat",
-      "androidx/car/ui/recyclerview/widget/LinearLayoutManager": "androidx/recyclerview/widget/LinearLayoutManager",
-      "androidx/car/ui/recyclerview/widget/LinearSmoothScroller": "androidx/recyclerview/widget/LinearSmoothScroller",
-      "androidx/car/ui/recyclerview/widget/LinearSnapHelper": "androidx/recyclerview/widget/LinearSnapHelper",
-      "androidx/car/ui/appcompat/widget/ListPopupWindow": "androidx/appcompat/widget/ListPopupWindow",
-      "androidx/car/ui/appcompat/widget/MenuItemHoverListener": "androidx/appcompat/widget/MenuItemHoverListener",
-      "androidx/car/ui/appcompat/widget/MenuPopupWindow": "androidx/appcompat/widget/MenuPopupWindow",
-      "androidx/car/ui/recyclerview/widget/OpReorderer": "androidx/recyclerview/widget/OpReorderer",
-      "androidx/car/ui/recyclerview/widget/OrientationHelper": "androidx/recyclerview/widget/OrientationHelper",
-      "androidx/car/ui/recyclerview/widget/PagerSnapHelper": "androidx/recyclerview/widget/PagerSnapHelper",
-      "androidx/car/ui/appcompat/widget/PopupMenu": "androidx/appcompat/widget/PopupMenu",
-      "androidx/car/ui/recyclerview/widget/RecyclerView": "androidx/recyclerview/widget/RecyclerView",
-      "androidx/car/ui/recyclerview/widget/RecyclerViewAccessibilityDelegate": "androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate",
-      "androidx/car/ui/appcompat/widget/ResourcesWrapper": "androidx/appcompat/widget/ResourcesWrapper",
-      "androidx/car/ui/appcompat/widget/RtlSpacingHelper": "androidx/appcompat/widget/RtlSpacingHelper",
-      "androidx/car/ui/recyclerview/widget/ScrollbarHelper": "androidx/recyclerview/widget/ScrollbarHelper",
-      "androidx/car/ui/appcompat/widget/ScrollingTabContainerView": "androidx/appcompat/widget/ScrollingTabContainerView",
-      "androidx/car/ui/appcompat/widget/SearchView": "androidx/appcompat/widget/SearchView",
-      "androidx/car/ui/appcompat/widget/ShareActionProvider": "androidx/appcompat/widget/ShareActionProvider",
-      "androidx/car/ui/recyclerview/widget/SimpleItemAnimator": "androidx/recyclerview/widget/SimpleItemAnimator",
-      "androidx/car/ui/recyclerview/widget/SnapHelper": "androidx/recyclerview/widget/SnapHelper",
-      "androidx/car/ui/recyclerview/widget/StaggeredGridLayoutManager": "androidx/recyclerview/widget/StaggeredGridLayoutManager",
-      "androidx/car/ui/appcompat/widget/SuggestionsAdapter": "androidx/appcompat/widget/SuggestionsAdapter",
-      "androidx/car/ui/appcompat/widget/SwitchCompat": "androidx/appcompat/widget/SwitchCompat",
-      "androidx/car/ui/appcompat/widget/ThemeUtils": "androidx/appcompat/widget/ThemeUtils",
-      "androidx/car/ui/appcompat/widget/ThemedSpinnerAdapter": "androidx/appcompat/widget/ThemedSpinnerAdapter",
-      "androidx/car/ui/appcompat/widget/TintContextWrapper": "androidx/appcompat/widget/TintContextWrapper",
-      "androidx/car/ui/appcompat/widget/TintInfo": "androidx/appcompat/widget/TintInfo",
-      "androidx/car/ui/appcompat/widget/TintResources": "androidx/appcompat/widget/TintResources",
-      "androidx/car/ui/appcompat/widget/TintTypedArray": "androidx/appcompat/widget/TintTypedArray",
-      "androidx/car/ui/appcompat/widget/Toolbar": "androidx/appcompat/widget/Toolbar",
-      "androidx/car/ui/appcompat/widget/ToolbarWidgetWrapper": "androidx/appcompat/widget/ToolbarWidgetWrapper",
-      "androidx/car/ui/appcompat/widget/TooltipCompat": "androidx/appcompat/widget/TooltipCompat",
-      "androidx/car/ui/appcompat/widget/TooltipCompatHandler": "androidx/appcompat/widget/TooltipCompatHandler",
-      "androidx/car/ui/appcompat/widget/TooltipPopup": "androidx/appcompat/widget/TooltipPopup",
-      "androidx/car/ui/appcompat/widget/VectorEnabledTintResources": "androidx/appcompat/widget/VectorEnabledTintResources",
-      "androidx/car/ui/recyclerview/widget/ViewBoundsCheck": "androidx/recyclerview/widget/ViewBoundsCheck",
-      "androidx/car/ui/recyclerview/widget/ViewInfoStore": "androidx/recyclerview/widget/ViewInfoStore",
-      "androidx/car/ui/appcompat/widget/ViewStubCompat": "androidx/appcompat/widget/ViewStubCompat",
-      "androidx/car/ui/appcompat/widget/ViewUtils": "androidx/appcompat/widget/ViewUtils",
-      "androidx/car/ui/appcompat/widget/WithHint": "androidx/appcompat/widget/WithHint",
-      "androidx/car/ui/recyclerview/widget/ItemTouchHelper": "androidx/recyclerview/widget/ItemTouchHelper",
-      "androidx/car/ui/recyclerview/widget/ItemTouchUIUtil": "androidx/recyclerview/widget/ItemTouchUIUtil",
-      "androidx/car/ui/recyclerview/widget/ItemTouchUIUtilImpl": "androidx/recyclerview/widget/ItemTouchUIUtilImpl",
-      "androidx/car/ui/recyclerview/widget/SortedListAdapterCallback": "androidx/recyclerview/widget/SortedListAdapterCallback",
-      "androidx/car/ui/activity/Cancellable": "androidx/activity/Cancellable",
-      "androidx/car/ui/activity/ComponentActivity": "androidx/activity/ComponentActivity",
-      "androidx/car/ui/activity/ImmLeaksCleaner": "androidx/activity/ImmLeaksCleaner",
-      "androidx/car/ui/activity/OnBackPressedCallback": "androidx/activity/OnBackPressedCallback",
-      "androidx/car/ui/activity/OnBackPressedDispatcher": "androidx/activity/OnBackPressedDispatcher",
-      "androidx/car/ui/activity/OnBackPressedDispatcherOwner": "androidx/activity/OnBackPressedDispatcherOwner",
-      "androidx/car/ui/activity/result/ActivityResultCallback": "androidx/activity/result/ActivityResultCallback",
-      "androidx/car/ui/activity/result/ActivityResultCaller": "androidx/activity/result/ActivityResultCaller",
-      "androidx/car/ui/activity/result/ActivityResultLauncher": "androidx/activity/result/ActivityResultLauncher",
-      "androidx/car/ui/activity/result/ActivityResultRegistry": "androidx/activity/result/ActivityResultRegistry",
-      "androidx/car/ui/activity/result/ActivityResultRegistryOwner": "androidx/activity/result/ActivityResultRegistryOwner",
-      "androidx/car/ui/activity/result/contract/ActivityResultContract": "androidx/activity/result/contract/ActivityResultContract",
-      "androidx/car/ui/savedstate/Recreator": "androidx/savedstate/Recreator",
-      "androidx/car/ui/savedstate/SavedStateRegistry": "androidx/savedstate/SavedStateRegistry",
-      "androidx/car/ui/savedstate/SavedStateRegistryController": "androidx/savedstate/SavedStateRegistryController",
-      "androidx/car/ui/savedstate/SavedStateRegistryOwner": "androidx/savedstate/SavedStateRegistryOwner",
-      "androidx/car/ui/savedstate/ViewTreeSavedStateRegistryOwner": "androidx/savedstate/ViewTreeSavedStateRegistryOwner"
-    }
-  },
-  "proGuardMap": {
-    "rules": {
-      "androidx/{any}Parcelizer": [
-        "androidx/{any}Parcelizer"
-      ],
-      "android/support/{any}": [
-        "android/support/{any}",
-        "androidx/{any}"
-      ],
-      "android/support{any}": [
-        "android/support{any}",
-        "androidx{any}"
-      ],
-      "android/support/v*/{any}": [
-        "android/support/**",
-        "androidx/{any}"
-      ],
-      "android/support/v4/{any}": [
-        "android/support/v4/{any}",
-        "androidx/{any}"
-      ],
-      "android/support/v4/view/{any}": [
-        "androidx/customview/view/{any}",
-        "androidx/core/view/{any}",
-        "androidx/asynclayoutinflater/{any}",
-        "androidx/viewpager/{any}",
-        "androidx/interpolator/{any}"
-      ],
-      "android/support/v4/media/{any}": [
-        "androidx/media/{any}",
-        "android/support/v4/{any}"
-      ],
-      "android/support/v7/{any}": [
-        "androidx/appcompat/{any}",
-        "androidx/mediarouter/{any}",
-        "androidx/cardview/{any}",
-        "androidx/palette/{any}",
-        "androidx/gridlayout/{any}",
-        "androidx/preference/{any}"
-      ],
-      "android/support/v7/widget/{any}": [
-        "androidx/appcompat/widget/{any}",
-        "androidx/recyclerview/widget/{any}",
-        "androidx/cardview/widget/{any}"
-      ],
-      "android/support/v7/preference/{any}": [
-        "androidx/preference/{any}"
-      ],
-      "android/support/v14/preference/{any}": [
-        "androidx/preference/{any}"
-      ],
-      "android/support/v17/preference/{any}": [
-        "androidx/leanback/preference/{any}"
-      ],
-      "android/support/v17/leanback/{any}": [
-        "androidx/leanback/{any}"
-      ],
-      "android/support/design/widget/{any}": [
-        "androidx/coordinatorlayout/widget/{any}",
-        "com/google/android/material/**"
-      ],
-      "android/support/design/{any}": [
-        "androidx/coordinatorlayout/**",
-        "com/google/android/material/**"
-      ],
-      "android/support/design/internal/{any}": [
-        "com/google/android/material/{any}"
-      ],
-      "android/support/car/{any}": [
-        "androidx/car/{any}"
-      ],
-      "android/arch/persistence/room/paging/{any}": [
-        "androidx/room/paging/{any}"
-      ],
-      "android/support/v7/internal/widget/ActionBarView${any}": [
-        "androidx/appcompat/widget/AbsActionBarView${any}"
-      ],
-      "android/support/v4/view/MenuItemCompat/*": [
-        "androidx/core/view/MenuItemCompat/*"
-      ],
-      "Android{any}": [
-        "Android{any}"
-      ]
-    }
-  },
-  "stringsMap": {
-    "types": {
-      "android/support/v13/view/inputmethod/EditorInfoCompat/CONTENT_MIME_TYPES": "android/support/v13/view/inputmethod/EditorInfoCompat/CONTENT_MIME_TYPES",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/COMMIT_CONTENT": "android/support/v13/view/inputmethod/InputConnectionCompat/COMMIT_CONTENT",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_DESCRIPTION": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_DESCRIPTION",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_FLAGS": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_FLAGS",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_LINK_URI": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_LINK_URI",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_OPTS": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_OPTS",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_RESULT_RECEIVER": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_RESULT_RECEIVER",
-      "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_URI": "android/support/v13/view/inputmethod/InputConnectionCompat/CONTENT_URI",
-      "android/support/v4/app/EXTRA_CALLING_ACTIVITY": "android/support/v4/app/EXTRA_CALLING_ACTIVITY",
-      "android/support/v4/app/EXTRA_CALLING_PACKAGE": "android/support/v4/app/EXTRA_CALLING_PACKAGE",
-      "androidx/contentpager/content/wakelockid": "androidx/contentpager/content/wakelockid",
-      "androidx/core/app/EXTRA_CALLING_ACTIVITY": "androidx/core/app/EXTRA_CALLING_ACTIVITY",
-      "androidx/core/app/EXTRA_CALLING_PACKAGE": "androidx/core/app/EXTRA_CALLING_PACKAGE",
-      "androidx/core/view/inputmethod/EditorInfoCompat/CONTENT_MIME_TYPES": "androidx/core/view/inputmethod/EditorInfoCompat/CONTENT_MIME_TYPES",
-      "androidx/support/content/wakelockid": "androidx/support/content/wakelockid"
-    }
-  }
-}
diff --git a/car-ui-lib/car-ui-lib/build.gradle b/car-ui-lib/car-ui-lib/build.gradle
index 478a739..4785db1 100644
--- a/car-ui-lib/car-ui-lib/build.gradle
+++ b/car-ui-lib/car-ui-lib/build.gradle
@@ -63,6 +63,10 @@
         }
     }
 
+    buildFeatures {
+        buildConfig = false
+    }
+
     // This is the gradle equivalent of the libs: ["android.car"] in the Android.bp
     useLibrary 'android.car'
 
@@ -75,25 +79,25 @@
     compileOnly project(':oem-apis')
     api project(':car-rotary-lib')
     api 'androidx.annotation:annotation:1.2.0'
-    api 'androidx.appcompat:appcompat:1.2.0'
-    api 'androidx.constraintlayout:constraintlayout:2.0.4'
+    api 'androidx.appcompat:appcompat:1.3.1'
+    api 'androidx.constraintlayout:constraintlayout:2.1.0'
     api 'androidx.preference:preference:1.1.1'
-    api 'androidx.recyclerview:recyclerview:1.2.0'
-    api 'androidx.core:core:1.3.2'
+    api 'androidx.recyclerview:recyclerview:1.2.1'
+    api 'androidx.core:core:1.6.0'
     api "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
     implementation 'com.android.support:support-annotations:28.0.0'
 
     androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
     androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
-    androidTestImplementation "com.google.truth:truth:1.1.2"
-    androidTestImplementation "androidx.test.ext:junit:1.1.2"
+    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
     androidTestImplementation "org.mockito:mockito-core:2.19.0"
-    androidTestImplementation 'androidx.test:runner:1.3.0'
-    androidTestImplementation 'androidx.test:rules:1.3.0'
+    androidTestImplementation 'androidx.test:runner:1.4.0'
+    androidTestImplementation 'androidx.test:rules:1.4.0'
     // This is needed to be able to spy certain classes with Mockito
     // It's major/minor version must match Mockito's.
-    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.19.0'
+    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.1'
     // Required for instrumented tests
     androidTestImplementation 'com.android.support:support-annotations:28.0.0'
 }
diff --git a/car-ui-lib/car-ui-lib/proguard-rules.pro b/car-ui-lib/car-ui-lib/proguard-rules.pro
index 6b66955..da67b33 100644
--- a/car-ui-lib/car-ui-lib/proguard-rules.pro
+++ b/car-ui-lib/car-ui-lib/proguard-rules.pro
@@ -17,17 +17,17 @@
 
 # We dynamically link the oem apis, and proguard can't see them
 # when running, so it errors out without -dontwarn
--dontwarn com.android.car.ui.sharedlibrary.oemapis.**
+-dontwarn com.android.car.ui.plugin.oemapis.**
 
 # Required because the static lib doesn't call most of the methods
-# on adapters, but instead passes it to the shared lib, where they
+# on adapters, but instead passes it to the plugin, where they
 # are called. Since proguard can't even see that those methods are
-# overriding oem api interfaces (since the oem apis are dynmically
+# overriding oem api interfaces (since the oem apis are dynamically
 # linked and marked with -dontwarn), it thinks they're unused.
 -keep class com.android.car.ui.**AdapterV* {*;}
 
 # required for accessing oem apis
--keep class com.android.car.ui.sharedlibrarysupport.OemApiUtil {*;}
+-keep class com.android.car.ui.pluginsupport.OemApiUtil {*;}
 
 # Required for AppCompat instantiating our layout inflater factory,
 # Otherwise it will be obfuscated and the reference to it in xml won't match
@@ -36,3 +36,12 @@
 # Required for reflection code in CarUiInstaller
 -keep class com.android.car.ui.baselayout.Insets {*;}
 -keep class com.android.car.ui.core.BaseLayoutController {*;}
+
+# Required for car-lib APIs
+# One of the GAS apps is failing during obfuscation by CrossReferenceValidator.
+# The root cause is unknown, and the suggestion from the team was to
+# suppress this warning.
+-dontwarn android.car.Car
+
+# This is needed so proguard would ignore the missing content provider.
+-dontwarn com.android.car.ui.plugin.PluginNameProvider
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
index 21c8813..2d4bd3b 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/AndroidManifest.xml
@@ -16,10 +16,11 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.car.ui.test">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <application android:debuggable="true" android:theme="@style/Theme.CarUi.NoToolbar">
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.car.ui.TrulyEmptyActivity"
-            android:theme="@android:style/Theme.Material.NoActionBar"/>
+            android:theme="@style/Theme.CarUi"/>
         <activity android:name="com.android.car.ui.TestActivity" />
         <activity android:name="com.android.car.ui.recyclerview.CarUiRecyclerViewTestActivity" />
         <activity android:name="com.android.car.ui.imewidescreen.CarUiImeWideScreenTestActivity" />
@@ -33,7 +34,9 @@
         <activity android:name="com.android.car.ui.utils.ViewUtilsTestActivity" />
         <activity
             android:name="com.android.car.ui.toolbar.ToolbarTestActivity"
-            android:theme="@style/Theme.CarUi.WithToolbar"/>
+            android:theme="@style/Theme.CarUi.WithToolbar">
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
         <provider
             android:name="com.android.car.ui.core.SearchResultsProvider"
             android:authorities="${applicationId}.SearchResultsProvider"
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
index 119a6fd..1683c53 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/AlertDialogBuilderTest.java
@@ -22,16 +22,22 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.TargetApi;
+import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
 import android.database.Cursor;
 import android.view.View;
 
 import androidx.test.espresso.Root;
-import androidx.test.rule.ActivityTestRule;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.recyclerview.CarUiContentListItem;
 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
@@ -44,62 +50,171 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
 
+@TargetApi(MIN_TARGET_API)
 public class AlertDialogBuilderTest {
 
     @Rule
-    public ActivityTestRule<TestActivity> mActivityRule =
-            new ActivityTestRule<>(TestActivity.class);
+    public ActivityScenarioRule<TestActivity> mActivityRule =
+            new ActivityScenarioRule<>(TestActivity.class);
 
     @Test
-    public void test_AlertDialogBuilder_works() throws Throwable {
+    public void test_AlertDialogBuilder_works() {
         String title = "Test message from AlertDialogBuilder";
         String subtitle = "Subtitle from AlertDialogBuilder";
-        mActivityRule.runOnUiThread(() ->
-                new AlertDialogBuilder(mActivityRule.getActivity())
+        runOnUiThread(activity ->
+                new AlertDialogBuilder(activity)
                         .setMessage(title)
                         .setSubtitle(subtitle)
                         .show());
 
         AlertDialog dialog = checkDefaultButtonExists(true,
-                new AlertDialogBuilder(mActivityRule.getActivity())
+                runOnUiThread(activity -> new AlertDialogBuilder(activity)
                         .setMessage(title)
-                        .setSubtitle(subtitle));
+                        .setSubtitle(subtitle)));
         onView(withText(title))
-                .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+                .inRoot(RootWithDecorMatcher.get(dialog))
                 .check(matches(isDisplayed()));
         onView(withText(subtitle))
-                .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
+                .inRoot(RootWithDecorMatcher.get(dialog))
                 .check(matches(isDisplayed()));
     }
 
     @Test
-    public void test_showSingleListChoiceItem_StringArray_hidesDefaultButton() throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_simplePropertiesWithResources_work() {
+        AlertDialog dialog = runOnUiThread(activity ->
+                new AlertDialogBuilder(activity)
+                        .setTitle(R.string.title)
+                        .setSubtitle(R.string.subtitle)
+                        .setIcon(R.drawable.ic_launcher)
+                        .setMessage(R.string.message)
+                        .setPositiveButton(R.string.positive, (d, which) -> {})
+                        .setNegativeButton(R.string.negative, (d, which) -> {})
+                        .setNeutralButton(R.string.neutral, (d, which) -> {})
+                        .show());
+        assertNotNull(dialog);
+        onView(withText(R.string.title))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.subtitle))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.message))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.positive))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.negative))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText(R.string.neutral))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_simplePropertiesWithStrings_work() {
+        AlertDialog dialog = runOnUiThread(activity ->
+                new AlertDialogBuilder(activity)
+                        .setTitle("Title!!")
+                        .setSubtitle("Subtitle!!")
+                        .setIcon(activity.getDrawable(R.drawable.ic_launcher))
+                        .setMessage("Message!!")
+                        .setPositiveButton("Positive!!", (d, which) -> {})
+                        .setNegativeButton("Negative!!", (d, which) -> {})
+                        .setNeutralButton("Neutral!!", (d, which) -> {})
+                        .show());
+        assertNotNull(dialog);
+        onView(withText("Title!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Subtitle!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Message!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Positive!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Negative!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Neutral!!"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_setCursor_works() {
+        FakeCursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "items");
+        AlertDialog dialog = runOnUiThread(activity ->
+                new AlertDialogBuilder(activity)
+                        .setCursor(cursor, (d, which) -> {}, "items")
+                        .show());
+        assertNotNull(dialog);
+        onView(withText("Item 1"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Item 2"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_getContext_works() {
+        Context context = runOnUiThread(activity ->
+                new AlertDialogBuilder(activity).getContext());
+        assertNotNull(context);
+    }
+
+    @Test
+    public void test_setCarUiRadioButtons_works() {
+        CarUiRadioButtonListItem item1 = new CarUiRadioButtonListItem();
+        item1.setTitle("Item 1");
+        CarUiRadioButtonListItem item2 = new CarUiRadioButtonListItem();
+        item2.setTitle("Item 2");
+
+        AlertDialog dialog = runOnUiThread(activity ->
+                new AlertDialogBuilder(activity)
+                        .setSingleChoiceItems(
+                                new CarUiRadioButtonListItemAdapter(Arrays.asList(item1, item2)))
+                        .show());
+        assertNotNull(dialog);
+        onView(withText("Item 1"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+        onView(withText("Item 2"))
+                .inRoot(RootWithDecorMatcher.get(dialog))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_showSingleListChoiceItem_StringArray_hidesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
                 .setSingleChoiceItems(new CharSequence[]{"Item 1", "Item 2"}, 0,
-                        ((dialog, which) -> {
-                        }));
+                        (dialog, which) -> {}));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_showSingleListChoiceItem_StringArrayResource_hidesDefaultButton()
-            throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_showSingleListChoiceItem_StringArrayResource_hidesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
-                .setSingleChoiceItems(R.array.test_string_array, 0, ((dialog, which) -> {
-                }));
+                .setSingleChoiceItems(R.array.test_string_array, 0, (dialog, which) -> {}));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_showSingleListChoiceItem_CarUiRadioButtonListItemAdapter_forcesDefaultButton()
-            throws Throwable {
+    public void test_singleListChoiceItems_CarUiRadioButtonListItemAdapter_forcesDefaultButton() {
         CarUiRadioButtonListItem item1 = new CarUiRadioButtonListItem();
         item1.setTitle("Item 1");
         CarUiRadioButtonListItem item2 = new CarUiRadioButtonListItem();
@@ -109,48 +224,44 @@
 
         CarUiRadioButtonListItemAdapter adapter = new CarUiRadioButtonListItemAdapter(
                 Arrays.asList(item1, item2, item3));
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
-                .setSingleChoiceItems(adapter);
+                .setSingleChoiceItems(adapter));
 
         checkDefaultButtonExists(true, builder);
     }
 
     @Test
-    public void test_showSingleListChoiceItem_cursor_hidesDefaultButton() throws Throwable {
+    public void test_showSingleListChoiceItem_cursor_hidesDefaultButton() {
         Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "ColumnName");
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setTitle("Title")
                 .setAllowDismissButton(false)
-                .setSingleChoiceItems(cursor, 0, "ColumnName", ((dialog, which) -> {
-                }));
+                .setSingleChoiceItems(cursor, 0, "ColumnName", (dialog, which) -> {}));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_setItems_StringArrayResource_hidesDefaultButton() throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_setItems_StringArrayResource_hidesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
-                .setItems(R.array.test_string_array, ((dialog, which) -> {
-                }));
+                .setItems(R.array.test_string_array, (dialog, which) -> {}));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_setItems_StringArray_hidesDefaultButton() throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_setItems_StringArray_hidesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
-                .setItems(new CharSequence[]{"Item 1", "Item 2"}, ((dialog, which) -> {
-                }));
+                .setItems(new CharSequence[]{"Item 1", "Item 2"}, (dialog, which) -> {}));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_setAdapter_hidesDefaultButton()
-            throws Throwable {
+    public void test_setAdapter_hidesDefaultButton() {
         CarUiContentListItem item1 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
         item1.setTitle("Item 1");
         CarUiContentListItem item2 = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
@@ -160,56 +271,49 @@
 
         CarUiListItemAdapter adapter = new CarUiListItemAdapter(
                 Arrays.asList(item1, item2, item3));
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
-                .setAdapter(adapter);
+                .setAdapter(adapter));
 
         checkDefaultButtonExists(false, builder);
     }
 
     @Test
-    public void test_multichoiceItems_StringArrayResource_forcesDefaultButton()
-            throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_multichoiceItems_StringArrayResource_forcesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
                 .setMultiChoiceItems(R.array.test_string_array, null,
-                        ((dialog, which, isChecked) -> {
-                        }));
+                        (dialog, which, isChecked) -> {}));
 
         checkDefaultButtonExists(true, builder);
     }
 
     @Test
-    public void test_multichoiceItems_StringArray_forcesDefaultButton()
-            throws Throwable {
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+    public void test_multichoiceItems_StringArray_forcesDefaultButton() {
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
                 .setMultiChoiceItems(new CharSequence[]{"Test 1", "Test 2"}, null,
-                        ((dialog, which, isChecked) -> {
-                        }));
+                        (dialog, which, isChecked) -> {}));
 
         checkDefaultButtonExists(true, builder);
     }
 
     @Test
-    public void test_multichoiceItems_Cursor_forcesDefaultButton()
-            throws Throwable {
+    public void test_multichoiceItems_Cursor_forcesDefaultButton() {
         Cursor cursor = new FakeCursor(Arrays.asList("Item 1", "Item 2"), "Label");
-        AlertDialogBuilder builder = new AlertDialogBuilder(mActivityRule.getActivity())
+        AlertDialogBuilder builder = runOnUiThread(activity -> new AlertDialogBuilder(activity)
                 .setAllowDismissButton(false)
                 .setMultiChoiceItems(cursor, "isChecked", "Label",
-                        ((dialog, which, isChecked) -> {
-                        }));
+                        (dialog, which, isChecked) -> {}));
 
         checkDefaultButtonExists(true, builder);
     }
 
-    private AlertDialog checkDefaultButtonExists(boolean shouldExist, AlertDialogBuilder builder)
-            throws Throwable {
+    private AlertDialog checkDefaultButtonExists(boolean shouldExist, AlertDialogBuilder builder) {
         AtomicBoolean didThrowException = new AtomicBoolean(false);
         AlertDialog[] result = new AlertDialog[1];
         RuntimeException[] exception = new RuntimeException[1];
-        mActivityRule.runOnUiThread(() -> {
+        mActivityRule.getScenario().onActivity(activity -> {
             try {
                 result[0] = builder.create();
                 result[0].show();
@@ -229,20 +333,35 @@
 
         if (shouldExist) {
             onView(withText(R.string.car_ui_alert_dialog_default_button))
-                    .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+                    .inRoot(RootWithDecorMatcher.get(result[0]))
                     .check(matches(isDisplayed()));
         } else {
             onView(withText(R.string.car_ui_alert_dialog_default_button))
-                    .inRoot(new RootWithDecorMatcher(result[0].getWindow().getDecorView()))
+                    .inRoot(RootWithDecorMatcher.get(result[0]))
                     .check(doesNotExist());
         }
 
         return result[0];
     }
 
+    private <T> T runOnUiThread(Function<Activity, T> block) {
+        ArrayList<T> result = new ArrayList<>();
+        mActivityRule.getScenario().onActivity(activity -> result.add(block.apply(activity)));
+
+        if (result.isEmpty()) {
+            return null;
+        } else {
+            return result.get(0);
+        }
+    }
+
     private static class RootWithDecorMatcher extends TypeSafeMatcher<Root> {
 
-        private View mView;
+        private final View mView;
+
+        static RootWithDecorMatcher get(Dialog dialog) {
+            return new RootWithDecorMatcher(dialog.getWindow().getDecorView());
+        }
 
         RootWithDecorMatcher(View view) {
             mView = view;
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemAtPositionViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemAtPositionViewAction.java
new file mode 100644
index 0000000..1008ea1
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemAtPositionViewAction.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import static org.hamcrest.Matchers.allOf;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.PerformException;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.util.HumanReadables;
+
+import com.android.car.ui.matchers.CarUiRecyclerViewMatcher;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Matcher;
+
+/**
+ * adapted from
+ * {@link androidx.test.espresso.contrib.RecyclerViewActions.ActionOnItemAtPositionViewAction}
+ */
+public final class ActionOnItemAtPositionViewAction<VH extends RecyclerView.ViewHolder>
+        implements ViewAction {
+    private final int mPosition;
+    private final ViewAction mViewAction;
+
+    public ActionOnItemAtPositionViewAction(int position, ViewAction viewAction) {
+        this.mPosition = position;
+        this.mViewAction = viewAction;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Matcher<View> getConstraints() {
+        return allOf(new CarUiRecyclerViewMatcher(), isDisplayed());
+    }
+
+    @Override
+    public String getDescription() {
+        return "actionOnItemAtPosition performing ViewAction: "
+                + mViewAction.getDescription()
+                + " on item at position: "
+                + mPosition;
+    }
+
+    @Override
+    public void perform(UiController uiController, View view) {
+        CarUiRecyclerView recyclerView = (CarUiRecyclerView) view;
+
+        new ScrollToPositionViewAction(mPosition).perform(uiController, view);
+        uiController.loopMainThreadUntilIdle();
+
+        @SuppressWarnings("unchecked")
+        VH viewHolderForPosition = (VH) recyclerView.findViewHolderForAdapterPosition(mPosition);
+        if (null == viewHolderForPosition) {
+            throw new PerformException.Builder()
+                    .withActionDescription(this.toString())
+                    .withViewDescription(HumanReadables.describe(view))
+                    .withCause(new IllegalStateException("No view holder at position: "
+                            + mPosition))
+                    .build();
+        }
+
+        View viewAtPosition = viewHolderForPosition.itemView;
+        if (null == viewAtPosition) {
+            throw new PerformException.Builder()
+                    .withActionDescription(this.toString())
+                    .withViewDescription(HumanReadables.describe(viewAtPosition))
+                    .withCause(new IllegalStateException("No view at position: " + mPosition))
+                    .build();
+        }
+
+        mViewAction.perform(uiController, viewAtPosition);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemViewAction.java
new file mode 100644
index 0000000..a0941c6
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ActionOnItemViewAction.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import static com.android.car.ui.actions.CarUiRecyclerViewActions.actionOnItemAtPosition;
+import static com.android.car.ui.actions.CarUiRecyclerViewActions.itemsMatching;
+
+import static org.hamcrest.Matchers.allOf;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.PerformException;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.util.HumanReadables;
+
+import com.android.car.ui.matchers.CarUiRecyclerViewMatcher;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Matcher;
+
+import java.util.List;
+
+/**
+ * Adapted from {@link androidx.test.espresso.contrib.RecyclerViewActions.ActionOnItemViewAction}
+ */
+public final class ActionOnItemViewAction<VH extends RecyclerView.ViewHolder>
+        implements RecyclerViewActions.PositionableRecyclerViewAction {
+
+    private static final int NO_POSITION = -1;
+
+    private final Matcher<VH> mViewHolderMatcher;
+    private final ViewAction mViewAction;
+    private final int mAtPosition;
+    private final ScrollToViewAction<VH> mScroller;
+
+    public ActionOnItemViewAction(Matcher<VH> viewHolderMatcher, ViewAction viewAction) {
+        this(viewHolderMatcher, viewAction, NO_POSITION);
+    }
+
+    public ActionOnItemViewAction(
+            Matcher<VH> viewHolderMatcher, ViewAction viewAction, int atPosition) {
+        this.mViewHolderMatcher = viewHolderMatcher;
+        this.mViewAction = viewAction;
+        this.mAtPosition = atPosition;
+        this.mScroller = new ScrollToViewAction<VH>(viewHolderMatcher, atPosition);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Matcher<View> getConstraints() {
+        return allOf(new CarUiRecyclerViewMatcher(), isDisplayed());
+    }
+
+    @Override
+    public RecyclerViewActions.PositionableRecyclerViewAction atPosition(int position) {
+        return new ActionOnItemViewAction<VH>(mViewHolderMatcher, mViewAction, position);
+    }
+
+    @Override
+    public String getDescription() {
+        if (mAtPosition == NO_POSITION) {
+            return String.format(
+                    "performing ViewAction: %s on item matching: %s",
+                    mViewAction.getDescription(), mViewHolderMatcher);
+
+        } else {
+            return String.format(
+                    "performing ViewAction: %s on %d-th item matching: %s",
+                    mViewAction.getDescription(), mAtPosition, mViewHolderMatcher);
+        }
+    }
+
+    @Override
+    public void perform(UiController uiController, View root) {
+        CarUiRecyclerView recyclerView = (CarUiRecyclerView) root;
+        try {
+            mScroller.perform(uiController, root);
+            uiController.loopMainThreadUntilIdle();
+            // the above scroller has checked bounds, dupes (maybe)
+            // and brought the element into screen.
+            int max = mAtPosition == NO_POSITION ? 2 : mAtPosition + 1;
+            int selectIndex = mAtPosition == NO_POSITION ? 0 : mAtPosition;
+            List<CarUiRecyclerViewActions.MatchedItem> matchedItems =
+                    itemsMatching(recyclerView, mViewHolderMatcher, max);
+            actionOnItemAtPosition(matchedItems.get(selectIndex).position, mViewAction)
+                    .perform(uiController, root);
+            uiController.loopMainThreadUntilIdle();
+        } catch (RuntimeException e) {
+            throw new PerformException.Builder()
+                    .withActionDescription(this.getDescription())
+                    .withViewDescription(HumanReadables.describe(root))
+                    .withCause(e)
+                    .build();
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/CarUiRecyclerViewActions.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/CarUiRecyclerViewActions.java
new file mode 100644
index 0000000..0ab83ad
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/CarUiRecyclerViewActions.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import android.util.SparseArray;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.util.HumanReadables;
+
+import com.android.car.ui.matchers.ViewHolderMatcher;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CarUiRecyclerViewActions {
+    public static ViewAction scrollToPosition(int position) {
+        return new ScrollToPositionViewAction(position);
+    }
+
+    public static <VH extends RecyclerView.ViewHolder>
+            RecyclerViewActions.PositionableRecyclerViewAction scrollTo(
+                final Matcher<View> itemViewMatcher) {
+        Matcher<VH> viewHolderMatcher = viewHolderMatcher(itemViewMatcher);
+        return new ScrollToViewAction<VH>(viewHolderMatcher);
+    }
+
+    public static <VH extends RecyclerView.ViewHolder>
+            RecyclerViewActions.PositionableRecyclerViewAction actionOnItem(
+                    final Matcher<View> itemViewMatcher, final ViewAction viewAction) {
+        Matcher<VH> viewHolderMatcher = new ViewHolderMatcher(itemViewMatcher);
+        return new ActionOnItemViewAction<VH>(viewHolderMatcher, viewAction);
+    }
+
+    public static <VH extends RecyclerView.ViewHolder> ViewAction actionOnItemAtPosition(
+            final int position, final ViewAction viewAction) {
+        return new ActionOnItemAtPositionViewAction<VH>(position, viewAction);
+    }
+
+    public static <T extends VH, VH extends RecyclerView.ViewHolder> List<MatchedItem>
+            itemsMatching(final CarUiRecyclerView recyclerView,
+                          final Matcher<VH> viewHolderMatcher, int max) {
+        final RecyclerView.Adapter<T> adapter = (RecyclerView.Adapter<T>) recyclerView.getAdapter();
+        SparseArray<VH> viewHolderCache = new SparseArray<VH>();
+        List<MatchedItem> matchedItems = new ArrayList<MatchedItem>();
+        for (int position = 0; position < adapter.getItemCount(); position++) {
+            int itemType = adapter.getItemViewType(position);
+            VH cachedViewHolder = viewHolderCache.get(itemType);
+            // Create a view holder per type if not exists
+            if (null == cachedViewHolder) {
+                // Return a view created with the app context so that a LayoutInflator created from
+                // this view can find resources as expected.
+                FrameLayout fakeParent = new FrameLayout(recyclerView.getContext());
+                cachedViewHolder = adapter.createViewHolder(fakeParent, itemType);
+                viewHolderCache.put(itemType, cachedViewHolder);
+            }
+            // Bind data to ViewHolder and apply matcher to view descendants.
+            adapter.bindViewHolder((T) cachedViewHolder, position);
+            if (viewHolderMatcher.matches(cachedViewHolder)) {
+                matchedItems.add(
+                        new MatchedItem(
+                                position,
+                                HumanReadables.getViewHierarchyErrorMessage(
+                                        cachedViewHolder.itemView,
+                                        null,
+                                        "\n\n*** Matched ViewHolder item at position: "
+                                                + position + " ***",
+                                        null)));
+                adapter.onViewRecycled((T) cachedViewHolder);
+                if (matchedItems.size() == max) {
+                    break;
+                }
+            } else {
+                adapter.onViewRecycled((T) cachedViewHolder);
+            }
+        }
+        return matchedItems;
+    }
+
+    /**
+     * Wrapper for matched items in recycler view which contains position and description of matched
+     * view.
+     */
+    static class MatchedItem {
+        public final int position;
+        public final String description;
+
+        private MatchedItem(int position, String description) {
+            this.position = position;
+            this.description = description;
+        }
+
+        @Override
+        public String toString() {
+            return description;
+        }
+    }
+
+    /**
+     * Adapted from {@link androidx.test.espresso.contrib.RecyclerViewActions.viewHolderMatcher}
+     */
+    private static <VH extends RecyclerView.ViewHolder> Matcher<VH> viewHolderMatcher(
+            final Matcher<View> itemViewMatcher) {
+        return new TypeSafeMatcher<VH>() {
+            @Override
+            public boolean matchesSafely(RecyclerView.ViewHolder viewHolder) {
+                return itemViewMatcher.matches(viewHolder.itemView);
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("holder with view: ");
+                itemViewMatcher.describeTo(description);
+            }
+        };
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToPositionViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToPositionViewAction.java
new file mode 100644
index 0000000..3941ea2
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToPositionViewAction.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import static org.hamcrest.Matchers.allOf;
+
+import android.view.View;
+
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+
+import com.android.car.ui.matchers.CarUiRecyclerViewMatcher;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Matcher;
+
+/**
+ * {@link ViewAction} which scrolls {@link CarUiRecyclerView} to a given position. See {@link
+ * RecyclerViewActions#scrollToPosition(int)} for more details.
+ */
+public final class ScrollToPositionViewAction implements ViewAction {
+    private final int mPosition;
+
+    public ScrollToPositionViewAction(int position) {
+        this.mPosition = position;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Matcher<View> getConstraints() {
+        return allOf(new CarUiRecyclerViewMatcher(), isDisplayed());
+    }
+
+    @Override
+    public String getDescription() {
+        return "scroll RecyclerView to position: " + mPosition;
+    }
+
+    @Override
+    public void perform(UiController uiController, View view) {
+        CarUiRecyclerView recyclerView = (CarUiRecyclerView) view;
+        recyclerView.scrollToPosition(mPosition);
+        uiController.loopMainThreadUntilIdle();
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToViewAction.java
new file mode 100644
index 0000000..ff74c6f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ScrollToViewAction.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import static com.android.car.ui.actions.CarUiRecyclerViewActions.itemsMatching;
+
+import static org.hamcrest.Matchers.allOf;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.PerformException;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.contrib.RecyclerViewActions;
+import androidx.test.espresso.util.HumanReadables;
+
+import com.android.car.ui.matchers.CarUiRecyclerViewMatcher;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Matcher;
+
+import java.util.List;
+
+/**
+ * Adapted from {@link androidx.test.espresso.contrib.RecyclerViewActions.ScrollToViewAction}
+ */
+public final class ScrollToViewAction<VH extends RecyclerView.ViewHolder>
+        implements RecyclerViewActions.PositionableRecyclerViewAction {
+
+    private static final int NO_POSITION = -1;
+
+    private final Matcher<VH> mViewHolderMatcher;
+    private final int mAtPosition;
+
+    public ScrollToViewAction(Matcher<VH> viewHolderMatcher) {
+        this(viewHolderMatcher, NO_POSITION);
+    }
+
+    public ScrollToViewAction(Matcher<VH> viewHolderMatcher, int atPosition) {
+        this.mViewHolderMatcher = viewHolderMatcher;
+        this.mAtPosition = atPosition;
+    }
+
+    @Override
+    public RecyclerViewActions.PositionableRecyclerViewAction atPosition(int position) {
+        return new ScrollToViewAction<VH>(mViewHolderMatcher, position);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Matcher<View> getConstraints() {
+        return allOf(new CarUiRecyclerViewMatcher(), isDisplayed());
+    }
+
+    @Override
+    public String getDescription() {
+        if (mAtPosition == NO_POSITION) {
+            return "scroll RecyclerView to: " + mViewHolderMatcher;
+        } else {
+            return String.format(
+                    "scroll RecyclerView to the: %dth matching %s.",
+                    mAtPosition,
+                    mViewHolderMatcher);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void perform(UiController uiController, View view) {
+        CarUiRecyclerView recyclerView = (CarUiRecyclerView) view;
+        try {
+            int maxMatches = mAtPosition == NO_POSITION ? 2 : mAtPosition + 1;
+            int selectIndex = mAtPosition == NO_POSITION ? 0 : mAtPosition;
+            List<CarUiRecyclerViewActions.MatchedItem> matchedItems = itemsMatching(recyclerView,
+                    mViewHolderMatcher, maxMatches);
+
+            if (selectIndex >= matchedItems.size()) {
+                throw new RuntimeException(
+                        String.format(
+                                "Found %d items matching %s, but position %d was requested.",
+                                matchedItems.size(), mViewHolderMatcher.toString(), mAtPosition));
+            }
+            if (mAtPosition == NO_POSITION && matchedItems.size() == 2) {
+                StringBuilder ambiguousViewError = new StringBuilder();
+                ambiguousViewError.append(
+                        String.format("Found more than one sub-view matching %s",
+                                mViewHolderMatcher));
+                for (CarUiRecyclerViewActions.MatchedItem item : matchedItems) {
+                    ambiguousViewError.append(item + "\n");
+                }
+                throw new RuntimeException(ambiguousViewError.toString());
+            }
+            recyclerView.scrollToPosition(matchedItems.get(selectIndex).position);
+            uiController.loopMainThreadUntilIdle();
+        } catch (RuntimeException e) {
+            throw new PerformException.Builder()
+                    .withActionDescription(this.getDescription())
+                    .withViewDescription(HumanReadables.describe(view))
+                    .withCause(e)
+                    .build();
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java
index e6847ed..de151bc 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/ViewActions.java
@@ -32,6 +32,14 @@
         return new WaitForViewAction(matcher, 500);
     }
 
+    public static ViewAction waitForNoMatchingView(Matcher<View> matcher, long waitTimeMillis) {
+        return new WaitForNoMatchingViewAction(matcher, waitTimeMillis);
+    }
+
+    public static ViewAction waitForNoMatchingView(Matcher<View> matcher) {
+        return new WaitForNoMatchingViewAction(matcher, 500);
+    }
+
     public static ViewAction setProgress(int progress) {
         return new SetProgressViewAction(progress);
     }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/WaitForNoMatchingViewAction.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/WaitForNoMatchingViewAction.java
new file mode 100644
index 0000000..7ce2c8b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/actions/WaitForNoMatchingViewAction.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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.ui.actions;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+
+import android.view.View;
+
+import androidx.test.espresso.PerformException;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.util.HumanReadables;
+import androidx.test.espresso.util.TreeIterables;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+
+import java.util.concurrent.TimeoutException;
+
+public class WaitForNoMatchingViewAction implements ViewAction {
+    private Matcher<View> mMatcher;
+    private long mWaitTimeMillis;
+
+    public WaitForNoMatchingViewAction(Matcher<View> matcher, long waitTimeMillis) {
+        mMatcher = matcher;
+        mWaitTimeMillis = waitTimeMillis;
+    }
+
+    @Override
+    public Matcher<View> getConstraints() {
+        return isRoot();
+    }
+
+    @Override
+    public String getDescription() {
+        Description description = new StringDescription();
+        mMatcher.describeTo(description);
+        return "wait at most " + mWaitTimeMillis + " milliseconds for no views matching "
+                + description.toString();
+    }
+
+    @Override
+    public void perform(UiController uiController, View view) {
+        uiController.loopMainThreadUntilIdle();
+        final long startTime = System.currentTimeMillis();
+        final long endTime = startTime + mWaitTimeMillis;
+
+        do {
+            boolean isViewFound = false;
+            for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
+                if (mMatcher.matches(child)) {
+                    isViewFound = true;
+                    break;
+                }
+            }
+
+            if (!isViewFound) {
+                return;
+            }
+
+            uiController.loopMainThreadForAtLeast(50);
+        }
+        while (System.currentTimeMillis() < endTime);
+
+        throw new PerformException.Builder()
+                .withActionDescription(this.getDescription())
+                .withViewDescription(HumanReadables.describe(view))
+                .withCause(new TimeoutException())
+                .build();
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/appstyledview/AppStyledDialogControllerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/appstyledview/AppStyledDialogControllerTest.java
index ca5bc1d..1a2ddcc 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/appstyledview/AppStyledDialogControllerTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/appstyledview/AppStyledDialogControllerTest.java
@@ -35,8 +35,6 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.car.ui.TestActivity;
-import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledDismissListener;
-import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledVCloseClickListener;
 import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledViewNavIcon;
 import com.android.car.ui.test.R;
 
@@ -118,12 +116,12 @@
         View appStyledTestView = inflator.inflate(R.layout.app_styled_view_sample, null,
                 false);
 
-        AppStyledVCloseClickListener callback = mock(AppStyledVCloseClickListener.class);
+        Runnable callback = mock(Runnable.class);
 
         mActivityRule.runOnUiThread(() -> {
             mAppStyledDialogController.setContentView(appStyledTestView);
             mAppStyledDialogController.setNavIcon(AppStyledViewNavIcon.BACK);
-            mAppStyledDialogController.setOnCloseClickListener(callback);
+            mAppStyledDialogController.setOnNavIconClickListener(callback);
             mAppStyledDialogController.show();
         });
 
@@ -133,7 +131,7 @@
                 .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
                 .perform(click());
 
-        verify(callback).onClick();
+        verify(callback).run();
     }
 
     @Test
@@ -143,7 +141,7 @@
         View appStyledTestView = inflator.inflate(R.layout.app_styled_view_sample, null,
                 false);
 
-        AppStyledDismissListener callback = mock(AppStyledDismissListener.class);
+        Runnable callback = mock(Runnable.class);
 
         mActivityRule.runOnUiThread(() -> {
             mAppStyledDialogController.setContentView(appStyledTestView);
@@ -157,8 +155,6 @@
         onView(withId(R.id.car_ui_app_styled_view_icon_close))
                 .inRoot(new RootWithDecorMatcher(dialog.getWindow().getDecorView()))
                 .perform(click());
-
-        verify(callback).onDismiss();
     }
 
     private static class RootWithDecorMatcher extends TypeSafeMatcher<Root> {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/CarUiInstallerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/CarUiInstallerTest.java
new file mode 100644
index 0000000..051f88e
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/CarUiInstallerTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.ui.core;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.TargetApi;
+import android.content.ContentValues;
+import android.net.Uri;
+
+import org.junit.Test;
+
+@TargetApi(MIN_TARGET_API)
+public class CarUiInstallerTest {
+    @Test
+    public void test_CarUiInstallerCRUDMethods_DoNothing() {
+        CarUiInstaller installer = new CarUiInstaller();
+        assertNull(installer.query(Uri.parse("content://test"), null, null, null, null));
+        assertNull(installer.getType(Uri.parse("content://test")));
+        assertNull(installer.insert(Uri.parse("content://test"), new ContentValues()));
+        assertEquals(installer.delete(
+                Uri.parse("content://test"), "selection", null), 0);
+        assertEquals(installer.update(
+                Uri.parse("content://test"), new ContentValues(), "selection", null), 0);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/SearchResultsProviderTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/SearchResultsProviderTest.java
index dad8074..0be997c 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/SearchResultsProviderTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/core/SearchResultsProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.core;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.core.SearchResultsProvider.ITEM_ID;
 import static com.android.car.ui.core.SearchResultsProvider.PRIMARY_IMAGE_BLOB;
 import static com.android.car.ui.core.SearchResultsProvider.SECONDARY_IMAGE_BLOB;
@@ -23,6 +24,8 @@
 import static com.android.car.ui.core.SearchResultsProvider.SUBTITLE;
 import static com.android.car.ui.core.SearchResultsProvider.TITLE;
 
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -46,6 +49,7 @@
  * Unit tests for {@link SearchResultsProvider}.
  */
 @RunWith(AndroidJUnit4.class)
+@TargetApi(MIN_TARGET_API)
 public class SearchResultsProviderTest extends ProviderTestCase2<SearchResultsProvider> {
 
     public static final String AUTHORITY =
@@ -94,6 +98,7 @@
     }
 
     @Test
+    @SuppressLint("Range")
     public void query_shouldHaveValidData() {
         ContentValues values = getRecord();
 
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItemTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItemTest.java
new file mode 100644
index 0000000..6251b4d
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItemTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 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.ui.imewidescreen;
+
+import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.ui.recyclerview.CarUiContentListItem.Action;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link CarUiImeSearchListItem}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarUiImeSearchListItemTest {
+
+    private CarUiImeSearchListItem mCarUiImeSearchListItem;
+
+    @Before
+    public void setUp() {
+        mCarUiImeSearchListItem = new CarUiImeSearchListItem(Action.ICON);
+    }
+
+    @Test
+    public void setIconResId_shouldSetResId() {
+        int resId = 1234;
+        mCarUiImeSearchListItem.setIconResId(resId);
+        assertThat(mCarUiImeSearchListItem.getIconResId(), is(resId));
+    }
+
+    @Test
+    public void setSupplementalIconResId_shouldSetResId() {
+        int resId = 1234;
+        mCarUiImeSearchListItem.setSupplementalIconResId(resId);
+        assertThat(mCarUiImeSearchListItem.getSupplementalIconResId(), is(resId));
+    }
+
+    @Test
+    public void setIcon_shouldSetIcon() {
+        Drawable drawable = new BitmapDrawable();
+        mCarUiImeSearchListItem.setIcon(drawable);
+        assertThat(mCarUiImeSearchListItem.getIcon(), is(drawable));
+    }
+
+    @Test
+    public void setIcon_shouldThrowError() {
+        assertThrows("icon should be of type BitmapDrawable", RuntimeException.class,
+                () -> mCarUiImeSearchListItem.setIcon(getDrawable()));
+    }
+
+    @Test
+    public void setSupplementalIcon_shouldSetIcon() {
+        Drawable drawable = new BitmapDrawable();
+        mCarUiImeSearchListItem.setSupplementalIcon(drawable);
+        assertThat(mCarUiImeSearchListItem.getSupplementalIcon(), is(drawable));
+    }
+
+    @Test
+    public void setSupplementalIcon_shouldThrowError() {
+        assertThrows("icon should be of type BitmapDrawable", RuntimeException.class,
+                () -> mCarUiImeSearchListItem.setSupplementalIcon(getDrawable()));
+    }
+
+    private Drawable getDrawable() {
+        return new Drawable() {
+            @Override
+            public void draw(@NonNull Canvas canvas) {
+
+            }
+
+            @Override
+            public void setAlpha(int alpha) {
+
+            }
+
+            @Override
+            public void setColorFilter(@Nullable ColorFilter colorFilter) {
+
+            }
+
+            @Override
+            public int getOpacity() {
+                return PixelFormat.UNKNOWN;
+            }
+        };
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
index 71d721e..99c3aa4 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenControllerTest.java
@@ -26,6 +26,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
 import static com.android.car.ui.core.SearchResultsProvider.ITEM_ID;
 import static com.android.car.ui.core.SearchResultsProvider.SECONDARY_IMAGE_ID;
 import static com.android.car.ui.core.SearchResultsProvider.SUBTITLE;
@@ -41,8 +42,7 @@
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_SEARCH_RESULTS;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenTestActivity.sCarUiImeWideScreenController;
-
-import static com.google.common.base.Preconditions.checkNotNull;
+import static com.android.car.ui.matchers.CarUiRecyclerViewMatcher.atPosition;
 
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
@@ -79,13 +79,10 @@
 import android.view.inputmethod.InputConnection;
 import android.widget.FrameLayout;
 
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
+import androidx.annotation.RequiresApi;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.espresso.contrib.RecyclerViewActions;
-import androidx.test.espresso.matcher.BoundedMatcher;
 import androidx.test.espresso.matcher.ViewMatchers.Visibility;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -94,8 +91,6 @@
 import com.android.car.ui.test.R;
 import com.android.car.ui.utils.CarUiUtils;
 
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -110,6 +105,7 @@
  * Unit tests for {@link CarUiImeWideScreenController}.
  */
 @RunWith(AndroidJUnit4.class)
+@RequiresApi(TARGET_API_R)
 public class CarUiImeWideScreenControllerTest {
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
@@ -410,9 +406,7 @@
                 .check(matches(atPosition(0, hasDescendant(withText("Title")))));
         onView(withId(R.id.car_ui_wideScreenSearchResultList))
                 .check(matches(atPosition(0, hasDescendant(withText("SubTitle")))));
-        onView(withId(R.id.car_ui_wideScreenSearchResultList))
-                .perform(RecyclerViewActions.actionOnItem(hasDescendant(withText("Title")),
-                        click()));
+        onView((withText("Title"))).perform(click());
 
         verify(spy, times(1)).onItemClicked("1");
     }
@@ -482,29 +476,6 @@
         return bytes;
     }
 
-    private static Matcher<View> atPosition(final int position,
-            @NonNull final Matcher<View> itemMatcher) {
-        checkNotNull(itemMatcher);
-        return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
-            @Override
-            public void describeTo(Description description) {
-                description.appendText("has item at position " + position + ": ");
-                itemMatcher.describeTo(description);
-            }
-
-            @Override
-            protected boolean matchesSafely(final RecyclerView view) {
-                RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(
-                        position);
-                if (viewHolder == null) {
-                    // has no item on such position
-                    return false;
-                }
-                return itemMatcher.matches(viewHolder.itemView);
-            }
-        };
-    }
-
     private CarUiImeWideScreenController getController() {
         return new CarUiImeWideScreenController(mActivity, mInputMethodServiceMock) {
             @Override
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java
index 0a02471..277209d 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenTestActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.imewidescreen;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
+
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.inputmethodservice.ExtractEditText;
 import android.os.Bundle;
@@ -30,6 +33,7 @@
 /**
  * An {@link Activity} that mimics a wide screen IME and displays the template for testing.
  */
+@TargetApi(TARGET_API_R)
 public class CarUiImeWideScreenTestActivity extends Activity {
     public static CarUiImeWideScreenController sCarUiImeWideScreenController;
 
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/CarUiRecyclerViewMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/CarUiRecyclerViewMatcher.java
new file mode 100644
index 0000000..27a3d49
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/CarUiRecyclerViewMatcher.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.ui.matchers;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * A {@link org.hamcrest.Matcher} that checks the given View is assignable from
+ * {@link CarUiRecyclerView}
+ */
+public class CarUiRecyclerViewMatcher extends TypeSafeMatcher<View> {
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("is assignable from class: " + CarUiRecyclerView.class);
+    }
+
+    @Override
+    public boolean matchesSafely(View view) {
+        return CarUiRecyclerView.class.isAssignableFrom(view.getClass());
+    }
+
+    public static Matcher<View> atPosition(final int position,
+                                            @NonNull final Matcher<View> itemMatcher) {
+        return new CarUiRecyclerViewMatcher() {
+            @Override
+            public boolean matchesSafely(View view) {
+                if (!super.matchesSafely(view)) {
+                    return false;
+                }
+
+                RecyclerView.ViewHolder viewHolder = ((CarUiRecyclerView) view)
+                        .findViewHolderForAdapterPosition(position);
+                if (viewHolder == null) {
+                    // has no item on such position
+                    return false;
+                }
+                return itemMatcher.matches(viewHolder.itemView);
+            }
+        };
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
index 2030e0f..c32ee79 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/DrawableMatcher.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.matchers;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -29,35 +32,32 @@
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
-/* package */ class DrawableMatcher extends TypeSafeMatcher<View> {
+@TargetApi(MIN_TARGET_API)
+public class DrawableMatcher extends TypeSafeMatcher<View> {
 
-    private final Bitmap mBitmap;
+    private final Drawable mDrawable;
 
     DrawableMatcher(@NonNull Context context, @DrawableRes int drawableId) {
         this(context.getDrawable(drawableId));
     }
+
     DrawableMatcher(Drawable drawable) {
-        mBitmap = drawableToBitmap(drawable);
+        mDrawable = drawable;
     }
 
     @Override
-    protected boolean matchesSafely(View item) {
-        if (!(item instanceof ImageView) || !item.isShown()) {
+    protected boolean matchesSafely(View target) {
+        if (!(target instanceof ImageView) || !target.isShown()) {
             return false;
         }
 
-        ImageView imageView = (ImageView) item;
-
-        Bitmap bitmap = drawableToBitmap(imageView.getDrawable());
-        Bitmap otherBitmap = mBitmap;
-
-        if (bitmap == null && otherBitmap == null) {
-            return true;
-        } else if ((bitmap == null) != (otherBitmap == null)) {
-            return false;
-        }
-
-        return bitmap.sameAs(otherBitmap);
+        Drawable targetDrawable = ((ImageView) target).getDrawable();
+        Drawable.ConstantState targetState = targetDrawable.getConstantState();
+        Drawable.ConstantState expectedState = mDrawable.getConstantState();
+        // If the constant state is identical, they are using the same drawable resource.
+        // However, the opposite is not necessarily true.
+        return (expectedState.equals(targetState) || drawableToBitmap(mDrawable).sameAs(
+                drawableToBitmap(targetDrawable)));
     }
 
     private Bitmap drawableToBitmap(Drawable drawable) {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
index 5ab1a86..32e6e89 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/PaddingMatcher.java
@@ -16,11 +16,15 @@
 
 package com.android.car.ui.matchers;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.view.View;
 
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 
+@TargetApi(MIN_TARGET_API)
 public class PaddingMatcher extends TypeSafeMatcher<View> {
 
     public enum Side {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewHolderMatcher.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewHolderMatcher.java
new file mode 100644
index 0000000..a3892f8
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewHolderMatcher.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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.ui.matchers;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * Creates matcher for view holder with given item view matcher.
+ */
+public class ViewHolderMatcher<VH extends RecyclerView.ViewHolder> extends TypeSafeMatcher<VH> {
+
+    private final Matcher<View> mItemViewMatcher;
+
+    public ViewHolderMatcher(Matcher<View> itemViewMatcher) {
+        mItemViewMatcher = itemViewMatcher;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("is assignable from class: " + CarUiRecyclerView.class);
+    }
+
+    @Override
+    protected boolean matchesSafely(RecyclerView.ViewHolder viewHolder) {
+        return mItemViewMatcher.matches(viewHolder.itemView);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
index 43f8663..b4985a9 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/matchers/ViewMatchers.java
@@ -23,6 +23,7 @@
 import static org.hamcrest.Matchers.not;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import androidx.annotation.DrawableRes;
@@ -39,6 +40,11 @@
         return new DrawableMatcher(context, drawableId);
     }
 
+    public static Matcher<View> withDrawable(
+            @NonNull Drawable drawable) {
+        return new DrawableMatcher(drawable);
+    }
+
     public static Matcher<View> nthChildOfView(Matcher<View> parentMatcher, int n) {
         return new NthChildMatcher(parentMatcher, n);
     }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/pluginsupport/PluginSpecifierTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/pluginsupport/PluginSpecifierTest.java
new file mode 100644
index 0000000..9d917e2
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/pluginsupport/PluginSpecifierTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.ui.pluginsupport;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.TargetApi;
+import android.content.pm.PackageInfo;
+
+import androidx.test.core.content.pm.PackageInfoBuilder;
+
+import org.junit.Test;
+
+@TargetApi(MIN_TARGET_API)
+public class PluginSpecifierTest {
+
+    @Test
+    public void test_empty_pluginspecifier_matches_anything() {
+        PluginSpecifier pluginSpecifier = PluginSpecifier.builder()
+                .build();
+
+        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
+                .setPackageName("com.android.car.testplugin").build();
+        packageInfo.setLongVersionCode(100);
+
+        assertTrue(pluginSpecifier.matches(packageInfo));
+    }
+
+    @Test
+    public void test_pluginspecifier_doesnt_match_different_package_name() {
+        PluginSpecifier pluginSpecifier = PluginSpecifier.builder()
+                .setPackageName("com.android.car.testplugin")
+                .build();
+
+        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
+                .setPackageName("com.android.car.testplugin2").build();
+
+        assertFalse(pluginSpecifier.matches(packageInfo));
+    }
+
+    @Test
+    public void test_pluginspecifier_matches_same_package_name() {
+        PluginSpecifier pluginSpecifier = PluginSpecifier.builder()
+                .setPackageName("com.android.car.testplugin")
+                .build();
+
+        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
+                .setPackageName("com.android.car.testplugin").build();
+
+        assertTrue(pluginSpecifier.matches(packageInfo));
+    }
+
+    @Test
+    public void test_pluginspecifier_doesnt_match_versioncode() {
+        PluginSpecifier pluginSpecifier = PluginSpecifier.builder()
+                .setPackageName("com.android.car.testplugin")
+                .setMaxVersion(5)
+                .build();
+
+        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
+                .setPackageName("com.android.car.testplugin").build();
+        packageInfo.setLongVersionCode(6);
+
+        assertFalse(pluginSpecifier.matches(packageInfo));
+    }
+
+    @Test
+    public void test_pluginspecifier_matches_versioncode() {
+        PluginSpecifier pluginSpecifier = PluginSpecifier.builder()
+                .setPackageName("com.android.car.testplugin")
+                .setMaxVersion(5)
+                .build();
+
+        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
+                .setPackageName("com.android.car.testplugin").build();
+        packageInfo.setLongVersionCode(4);
+
+        assertTrue(pluginSpecifier.matches(packageInfo));
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
index 38a66ad..4048b65 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/NonFullscreenPreferenceFragmentTest.java
@@ -26,9 +26,11 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.withPadding;
 import static com.android.car.ui.matchers.ViewMatchers.withPaddingAtLeast;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -54,6 +56,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 
+@TargetApi(MIN_TARGET_API)
 public class NonFullscreenPreferenceFragmentTest {
 
     private static final String EXTRA_FULLSCREEN = "fullscreen";
@@ -142,10 +145,14 @@
             toolbar.setTitle(TOOLBAR_DEFAULT_TEXT);
 
             mIsFullScreen = getIntent().getBooleanExtra(EXTRA_FULLSCREEN, true);
+            Fragment fragment = new MyPreferenceFragment();
+            Bundle args = new Bundle();
+            args.putBoolean("IsFullScreen", mIsFullScreen);
+            fragment.setArguments(args);
             if (savedInstanceState == null) {
                 getSupportFragmentManager()
                         .beginTransaction()
-                        .replace(android.R.id.content, new MyPreferenceFragment(mIsFullScreen))
+                        .replace(android.R.id.content, fragment)
                         .commitNow();
             }
         }
@@ -168,10 +175,12 @@
 
     public static class MyPreferenceFragment extends PreferenceFragment {
 
-        private final boolean mIsFullScreen;
+        private boolean mIsFullScreen;
 
-        public MyPreferenceFragment(boolean isFullScreen) {
-            mIsFullScreen = isFullScreen;
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mIsFullScreen = getArguments().getBoolean("IsFullScreen");
         }
 
         @Override
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
index 0866334..c675df7 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTest.java
@@ -23,12 +23,14 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isFocused;
 import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.car.ui.actions.ViewActions.setProgress;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.withIndex;
 
 import static junit.framework.Assert.assertFalse;
@@ -44,15 +46,18 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.res.Resources;
+import android.view.KeyEvent;
 import android.view.View;
 
 import androidx.preference.CheckBoxPreference;
 import androidx.preference.DropDownPreference;
 import androidx.preference.EditTextPreference;
 import androidx.preference.Preference;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.car.ui.test.R;
@@ -68,6 +73,7 @@
 /**
  * Unit tests for {@link CarUiPreference}.
  */
+@TargetApi(MIN_TARGET_API)
 public class PreferenceTest {
 
     private PreferenceTestActivity mActivity;
@@ -154,6 +160,21 @@
                 .check(matches(isChecked()));
         onView(withIndex(withId(R.id.car_ui_list_item_radio_button_widget), 2))
                 .check(matches(isNotChecked()));
+
+        // Rotary focus test
+        // Return to main screen and turn off touch mode.
+        onView(withContentDescription("Back")).perform(click());
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
+        // Return to list preference screen. Requires two inputs to focus and then click.
+        InstrumentationRegistry.getInstrumentation()
+                .sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        InstrumentationRegistry.getInstrumentation()
+                .sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+        // Check that second option is selected and focused.
+        onView(withIndex(withId(R.id.car_ui_list_item_radio_button_widget), 1))
+                .check(matches(isChecked()));
+        onView(withIndex(withId(R.id.car_ui_list_item_touch_interceptor), 1))
+                .check(matches(isFocused()));
     }
 
     @Test
@@ -377,6 +398,39 @@
     }
 
     @Test
+    public void testSwitchPreference_clickableWhileDisabled() {
+        // Create switch preference and add it to screen.
+        CarUiSwitchPreference preference = new CarUiSwitchPreference(mActivity);
+        preference.setOrder(0);
+        preference.setKey("switch");
+        preference.setTitle(R.string.title_switch_preference);
+        preference.setSummary(R.string.summary_compound_button_preference);
+        preference.setEnabled(false);
+        preference.setClickableWhileDisabled(true);
+        mActivity.addPreference(preference);
+
+        // Scroll until switch preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("switch"));
+
+        // Check title and summary are displayed as expected.
+        onView(withIndex(withId(android.R.id.title), 0)).check(matches(
+                withText(mActivity.getString(R.string.title_switch_preference))));
+        onView(withIndex(withId(android.R.id.summary), 0)).check(matches(
+                withText(mActivity.getString(R.string.summary_compound_button_preference))));
+
+        assertTrue(preference.isClickableWhileDisabled());
+
+        // Set listener
+        Consumer<Preference> clickListener = mock(Consumer.class);
+        preference.setDisabledClickListener(clickListener);
+        assertEquals(clickListener, preference.getDisabledClickListener());
+
+        // Click on disabled preference
+        onView(withText(R.string.title_switch_preference)).perform(click());
+        verify(clickListener, times(1)).accept(preference);
+    }
+
+    @Test
     public void testRadioPreference() {
         // Create radio button preference and add it to screen.
         CarUiRadioButtonPreference preference = new CarUiRadioButtonPreference(mActivity);
@@ -651,6 +705,44 @@
     }
 
     @Test
+    public void testTwoActionIconPreference_clickableWhileDisabled() {
+        // Create CarUiTwoActionIconPreference preference and add it to screen.
+        CarUiTwoActionIconPreference preference = new CarUiTwoActionIconPreference(mActivity);
+        preference.setKey("twoaction");
+        preference.setTitle(R.string.title_twoaction_preference);
+        preference.setSummary(R.string.summary_twoaction_preference);
+        preference.setOrder(0);
+        preference.setSecondaryActionIcon(R.drawable.avd_show_password);
+        Runnable clickListener = mock(Runnable.class);
+        preference.setOnSecondaryActionClickListener(clickListener);
+        preference.setEnabled(false);
+        preference.setSecondaryActionEnabled(false);
+        preference.setClickableWhileDisabled(true);
+        mActivity.addPreference(preference);
+
+        // Scroll until CarUiTwoActionIconPreference preference button preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("twoaction"));
+
+        // Check title is displayed as expected.
+        onView(withText(R.string.title_twoaction_preference)).check(matches(isDisplayed()));
+        onView(withText(R.string.summary_twoaction_preference)).check(matches(isDisplayed()));
+
+        assertTrue(preference.isClickableWhileDisabled());
+
+        // Set listener
+        Consumer<Preference> disabledClickListener = mock(Consumer.class);
+        preference.setDisabledClickListener(disabledClickListener);
+        assertEquals(disabledClickListener, preference.getDisabledClickListener());
+
+        // Click on disabled preference
+        onView(withText(R.string.title_twoaction_preference)).perform(click());
+        // Click on disabled icon.
+        onView(withIndex(withId(com.android.car.ui.R.id.car_ui_second_action_container),
+                0)).perform(click());
+        verify(disabledClickListener, times(2)).accept(preference);
+    }
+
+    @Test
     public void testTwoActionTextPreference() {
         // Create CarUiTwoActionTextPreference preference and add it to screen.
         CarUiTwoActionTextPreference preference = new CarUiTwoActionTextPreference(mActivity);
@@ -730,6 +822,44 @@
     }
 
     @Test
+    public void testTwoActionTextPreference_clickableWhileDisabled() {
+        // Create CarUiTwoActionTextPreference preference and add it to screen.
+        CarUiTwoActionTextPreference preference = new CarUiTwoActionTextPreference(mActivity);
+        preference.setKey("twoaction");
+        preference.setTitle(R.string.title_twoaction_preference);
+        preference.setSummary(R.string.summary_twoaction_preference);
+        preference.setOrder(0);
+        preference.setSecondaryActionText(R.string.twoaction_secondary_text);
+        Runnable clickListener = mock(Runnable.class);
+        preference.setOnSecondaryActionClickListener(clickListener);
+        preference.setEnabled(false);
+        preference.setSecondaryActionEnabled(false);
+        preference.setClickableWhileDisabled(true);
+        mActivity.addPreference(preference);
+
+        // Scroll until CarUiTwoActionTextPreference preference button preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("twoaction"));
+
+        // Check title is displayed as expected.
+        onView(withText(R.string.title_twoaction_preference)).check(matches(isDisplayed()));
+        onView(withText(R.string.summary_twoaction_preference)).check(matches(isDisplayed()));
+
+        assertTrue(preference.isClickableWhileDisabled());
+
+        // Set listener
+        Consumer<Preference> disabledClickListener = mock(Consumer.class);
+        preference.setDisabledClickListener(disabledClickListener);
+        assertEquals(disabledClickListener, preference.getDisabledClickListener());
+
+        // Click on disabled preference
+        onView(withText(R.string.title_twoaction_preference)).perform(click());
+        // Click on disabled icon.
+        onView(withIndex(withId(com.android.car.ui.R.id.car_ui_second_action_container),
+                0)).perform(click());
+        verify(disabledClickListener, times(2)).accept(preference);
+    }
+
+    @Test
     public void testTwoActionSwitchPreference() {
         // Create CarUiTwoActionSwitchPreference preference and add it to screen.
         CarUiTwoActionSwitchPreference preference = new CarUiTwoActionSwitchPreference(mActivity);
@@ -788,6 +918,43 @@
     }
 
     @Test
+    public void testTwoActionSwitchPreference_clickableWhileDisabled() {
+        // Create CarUiTwoActionSwitchPreference preference and add it to screen.
+        CarUiTwoActionSwitchPreference preference = new CarUiTwoActionSwitchPreference(mActivity);
+        preference.setKey("twoaction");
+        preference.setTitle(R.string.title_twoaction_preference);
+        preference.setSummary(R.string.summary_twoaction_preference);
+        preference.setOrder(0);
+        Consumer<Boolean> clickListener = mock(Consumer.class);
+        preference.setOnSecondaryActionClickListener(clickListener);
+        preference.setEnabled(false);
+        preference.setSecondaryActionEnabled(false);
+        preference.setClickableWhileDisabled(true);
+        mActivity.addPreference(preference);
+
+        // Scroll until CarUiTwoActionSwitchPreference preference button preference is visible
+        mActivity.runOnUiThread(() -> mActivity.scrollToPreference("twoaction"));
+
+        // Check title is displayed as expected.
+        onView(withText(R.string.title_twoaction_preference)).check(matches(isDisplayed()));
+        onView(withText(R.string.summary_twoaction_preference)).check(matches(isDisplayed()));
+
+        assertTrue(preference.isClickableWhileDisabled());
+
+        // Set listener
+        Consumer<Preference> disabledClickListener = mock(Consumer.class);
+        preference.setDisabledClickListener(disabledClickListener);
+        assertEquals(disabledClickListener, preference.getDisabledClickListener());
+
+        // Click on disabled preference
+        onView(withText(R.string.title_twoaction_preference)).perform(click());
+        // Click on disabled icon.
+        onView(withIndex(withId(com.android.car.ui.R.id.car_ui_second_action_container),
+                0)).perform(click());
+        verify(disabledClickListener, times(2)).accept(preference);
+    }
+
+    @Test
     public void testEditTextPreference() {
         // Create CarUiEditTextPreference preference and add it to screen.
         CarUiEditTextPreference preference = new CarUiEditTextPreference(mActivity);
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java
index 631911d..44863b5 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/preference/PreferenceTestFragment.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
@@ -32,6 +35,7 @@
 /**
  * Test Fragment to load test preferences.
  */
+@TargetApi(MIN_TARGET_API)
 public class PreferenceTestFragment extends PreferenceFragment {
 
     @Override
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
index aa69a26..0699652 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiListItemTest.java
@@ -23,12 +23,14 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
 import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.isActivated;
+import static com.android.car.ui.matchers.ViewMatchers.withDrawable;
 
-import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -38,21 +40,22 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
-import android.widget.TextView;
+import android.annotation.TargetApi;
+import android.graphics.drawable.Drawable;
+import android.widget.CompoundButton;
 
-import androidx.core.content.ContextCompat;
-import androidx.test.core.app.ActivityScenario;
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
-import com.android.car.ui.CarUiText;
 import com.android.car.ui.R;
+import com.android.car.ui.core.CarUi;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -60,36 +63,31 @@
 /**
  * Unit tests for {@link CarUiListItem}.
  */
+@RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class CarUiListItemTest {
-    private static final CharSequence LONG_CHAR_SEQUENCE =
-            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
-                    + "incididunt ut labore et dolore magna aliqua. Netus et malesuada fames ac "
-                    + "turpis egestas maecenas pharetra convallis. At urna condimentum mattis "
-                    + "pellentesque id nibh tortor. Purus in mollis nunc sed id semper risus in. "
-                    + "Turpis massa tincidunt dui ut ornare lectus sit amet. Porttitor lacus "
-                    + "luctus accumsan tortor posuere ac. Augue eget arcu dictum varius. Massa "
-                    + "tempor nec feugiat nisl pretium fusce id velit ut. Fames ac turpis egestas"
-                    + " sed tempus urna et pharetra pharetra. Tellus orci ac auctor augue mauris "
-                    + "augue neque gravida. Purus viverra accumsan in nisl nisi scelerisque eu. "
-                    + "Ut lectus arcu bibendum at varius vel pharetra. Penatibus et magnis dis "
-                    + "parturient montes nascetur ridiculus mus. Suspendisse sed nisi lacus sed "
-                    + "viverra tellus in hac habitasse.";
-    private static final String ELLIPSIS = "…";
-
-    private CarUiRecyclerView mCarUiRecyclerView;
 
     @Rule
     public ActivityScenarioRule<CarUiRecyclerViewTestActivity> mActivityRule =
             new ActivityScenarioRule<>(CarUiRecyclerViewTestActivity.class);
 
-    private ActivityScenario<CarUiRecyclerViewTestActivity> mScenario;
+    @Parameterized.Parameters
+    public static Object[] data() {
+        // It's important to do not plugin first, so that the plugin will
+        // still be enabled when this test finishes
+        return new Object[]{false, true};
+    }
 
+    private CarUiRecyclerView mCarUiRecyclerView;
     private CarUiRecyclerViewTestActivity mActivity;
 
+    public CarUiListItemTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
+    }
+
     @Before
     public void setUp() {
-        mScenario = mActivityRule.getScenario();
-        mScenario.onActivity(activity -> {
+        mActivityRule.getScenario().onActivity(activity -> {
             mActivity = activity;
             mCarUiRecyclerView = mActivity.requireViewById(R.id.list);
         });
@@ -100,16 +98,14 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setTitle("Test title");
+        String title = "Test title";
+        item.setTitle(title);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_icon_container)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_action_container)).check(matches(not(isDisplayed())));
+        onView(withText(title)).check(matches(isDisplayed()));
     }
 
     @Test
@@ -117,39 +113,35 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody("Test body");
+        String body = "Test body";
+        item.setBody(body);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isEnabled()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(not(isActivated())));
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_icon_container)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_action_container)).check(matches(not(isDisplayed())));
+        onView(withText(body)).check(matches(isDisplayed()));
     }
 
     @Test
     public void testHeaderItemVisibility() {
         List<CarUiListItem> items = new ArrayList<>();
 
-        CharSequence title = "Test title";
-        CharSequence body = "Test body";
+        String title = "Test title";
+        String body = "Test body";
         CarUiListItem item = new CarUiHeaderListItem(title, body);
         items.add(item);
 
-        CharSequence title2 = "Test title2";
+        String title2 = "Test title2";
         item = new CarUiHeaderListItem(title2);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withText(title.toString())).check(matches(isDisplayed()));
-        onView(withText(body.toString())).check(matches(isDisplayed()));
-        onView(withText(title2.toString())).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
+        onView(withText(body)).check(matches(isDisplayed()));
+        onView(withText(title2)).check(matches(isDisplayed()));
     }
 
     @Test
@@ -157,14 +149,15 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody("Item that is disabled");
+        String body = "Item that is disabled";
+        item.setBody(body);
         item.setEnabled(false);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(not(isEnabled())));
+        onView(withText(body)).check(matches(not(isEnabled())));
     }
 
     @Test
@@ -172,14 +165,15 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody("Item that is disabled");
+        String body = "Item that is activated";
+        item.setBody(body);
         item.setActivated(true);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isActivated()));
+        onView(withText(body)).check(matches(isActivated()));
     }
 
     @Test
@@ -187,36 +181,38 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.CHEVRON);
-        item.setTitle("Test item with chevron");
+        String title = "Test item with chevron";
+        item.setTitle(title);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_icon_container)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_list_item_action_container)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
+        onView(withDrawable(mActivity, R.drawable.car_ui_icon_chevron)).check(
+                matches(isDisplayed()));
     }
 
     @Test
     public void testItemVisibility_withTitle_withBodyAndIcon() {
         List<CarUiListItem> items = new ArrayList<>();
 
+        Drawable close = mActivity.getDrawable(R.drawable.car_ui_icon_close);
+
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setTitle("Test title");
-        item.setBody("Test body");
-        item.setIcon(mActivity.getDrawable(R.drawable.car_ui_icon_close));
-        item.setPrimaryIconType(CarUiContentListItem.IconType.CONTENT);
+        String title = "Test title";
+        String body = "Test body";
+        item.setTitle(title);
+        item.setBody(body);
+        item.setIcon(close);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_icon_container)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_action_container)).check(matches(not(isDisplayed())));
+        onView(withText(title)).check(matches(isDisplayed()));
+        onView(withText(body)).check(matches(isDisplayed()));
+        onView(withDrawable(close)).check(matches(isDisplayed()));
     }
 
     @Test
@@ -227,28 +223,29 @@
                 CarUiContentListItem.OnCheckedChangeListener.class);
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.CHECK_BOX);
-        item.setTitle("Test item with checkbox");
+        String title = "Test item with checkbox";
+        item.setTitle(title);
         item.setOnCheckedChangeListener(mockOnCheckedChangeListener);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_action_divider)).check(matches(not(isDisplayed())));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // List item with checkbox should be initially unchecked.
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isNotChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
         // Clicks anywhere on the item should toggle the checkbox
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isChecked()));
+        onView(withText(title)).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
         // Check that onCheckChangedListener was invoked.
         verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, true);
 
-        // Uncheck checkbox with click on the action container
-        onView(withId(R.id.car_ui_list_item_action_container)).perform(click());
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isNotChecked()));
+        // Uncheck checkbox with click on the checkbox
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
         // Check that onCheckChangedListener was invoked.
         verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, false);
     }
@@ -261,28 +258,29 @@
                 CarUiContentListItem.OnCheckedChangeListener.class);
 
         CarUiContentListItem item = new CarUiCheckBoxListItem();
-        item.setTitle("Test item with checkbox");
+        String title = "Test item with checkbox";
+        item.setTitle(title);
         item.setOnCheckedChangeListener(mockOnCheckedChangeListener);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_action_divider)).check(matches(not(isDisplayed())));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // List item with checkbox should be initially unchecked.
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isNotChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
         // Clicks anywhere on the item should toggle the checkbox
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isChecked()));
+        onView(withText(title)).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
         // Check that onCheckChangedListener was invoked.
         verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, true);
 
-        // Uncheck checkbox with click on the action container
-        onView(withId(R.id.car_ui_list_item_action_container)).perform(click());
-        onView(withId(R.id.car_ui_list_item_checkbox_widget)).check(matches(isNotChecked()));
+        // Uncheck checkbox with click on the checkbox
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
         // Check that onCheckChangedListener was invoked.
         verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, false);
     }
@@ -292,26 +290,26 @@
         List<CarUiListItem> items = new ArrayList<>();
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.SWITCH);
-        item.setBody("Test item with switch");
+        String body = "Test item with switch";
+        item.setBody(body);
         item.setChecked(true);
         item.setActionDividerVisible(true);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_switch_widget)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_action_divider)).check(matches(isDisplayed()));
+        onView(withText(body)).check(matches(isDisplayed()));
 
         // List item with checkbox should be initially checked.
-        onView(withId(R.id.car_ui_list_item_switch_widget)).check(matches(isChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
         // Clicks anywhere on the item should toggle the switch
-        onView(withId(R.id.car_ui_list_item_switch_widget)).perform(click());
-        onView(withId(R.id.car_ui_list_item_switch_widget)).check(matches(isNotChecked()));
-        // Uncheck checkbox with click on the action container
-        onView(withId(R.id.car_ui_list_item_body)).perform(click());
-        onView(withId(R.id.car_ui_list_item_switch_widget)).check(matches(isChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
+        // Uncheck checkbox with click on the body text
+        onView(withText(body)).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
     }
 
     @Test
@@ -320,28 +318,28 @@
 
         CarUiContentListItem item = new CarUiContentListItem(
                 CarUiContentListItem.Action.RADIO_BUTTON);
-        item.setTitle("Test item with radio button");
+        String title = "Test item with radio button";
+        item.setTitle(title);
         item.setChecked(false);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_radio_button_widget)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // List item with checkbox should be initially not checked.
-        onView(withId(R.id.car_ui_list_item_radio_button_widget)).check(matches(isNotChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(
+                matches(isNotChecked()));
         // Clicks anywhere on the item should toggle the radio button.
-        onView(withId(R.id.car_ui_list_item_radio_button_widget)).perform(click());
-        onView(withId(R.id.car_ui_list_item_radio_button_widget)).check(matches(isChecked()));
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
 
         // Repeated clicks on a selected radio button should not toggle the element once checked.
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
-        onView(withId(R.id.car_ui_list_item_radio_button_widget)).check(matches(isChecked()));
+        onView(withText(title)).perform(click());
+        onView(allOf(instanceOf(CompoundButton.class), isDisplayed())).check(matches(isChecked()));
     }
 
-
     @Test
     public void testItem_withCompactLayout() {
         List<CarUiListItem> items = new ArrayList<>();
@@ -357,8 +355,8 @@
         mCarUiRecyclerView.post(
                 () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items, true)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(withText(titleText)));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(withText(bodyText)));
+        onView(withText(titleText)).check(matches(isDisplayed()));
+        onView(withText(bodyText)).check(matches(isDisplayed()));
     }
 
     @Test
@@ -368,28 +366,33 @@
         CarUiContentListItem.OnClickListener mockOnCheckedChangeListener = mock(
                 CarUiContentListItem.OnClickListener.class);
 
+        Drawable close = mActivity.getDrawable(R.drawable.car_ui_icon_close);
+
         CarUiContentListItem item = new CarUiContentListItem(
                 CarUiContentListItem.Action.NONE);
-        item.setIcon(mActivity.getDrawable(R.drawable.car_ui_icon_close));
+        item.setIcon(close);
         item.setPrimaryIconType(CarUiContentListItem.IconType.AVATAR);
-        item.setTitle("Test item with listener");
-        item.setBody("Body text");
+        String title = "Test item with listener";
+        String body = "Body text";
+        item.setTitle(title);
+        item.setBody(body);
         item.setOnItemClickedListener(mockOnCheckedChangeListener);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // Clicks anywhere on the item should toggle the listener
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
+        onView(withText(title)).perform(click());
         verify(mockOnCheckedChangeListener, times(1)).onClick(item);
 
-        onView(withId(R.id.car_ui_list_item_body)).perform(click());
+        onView(withText(body)).perform(click());
         verify(mockOnCheckedChangeListener, times(2)).onClick(item);
 
-        onView(withId(R.id.car_ui_list_item_icon_container)).perform(click());
+        onView(withDrawable(close)).check(matches(isDisplayed()));
+        onView(withDrawable(close)).perform(click());
         verify(mockOnCheckedChangeListener, times(3)).onClick(item);
     }
 
@@ -404,25 +407,30 @@
 
         CarUiContentListItem item = new CarUiContentListItem(
                 CarUiContentListItem.Action.ICON);
-        item.setTitle("Test item with two listeners");
+        String title = "Test item with two listeners";
+        item.setTitle(title);
         item.setOnItemClickedListener(clickListener);
+        Drawable close = mActivity.getDrawable(R.drawable.car_ui_icon_close);
+
+
         item.setSupplementalIcon(
-                mActivity.getDrawable(R.drawable.car_ui_icon_close),
+                close,
                 supplementalIconClickListener);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // Clicks anywhere on the item (except supplemental icon) should trigger the item click
         // listener.
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
+        onView(withText(title)).perform(click());
         verify(clickListener, times(1)).onClick(item);
         verify(supplementalIconClickListener, times(0)).onClick(item);
 
-        onView(withId(R.id.car_ui_list_item_supplemental_icon)).perform(click());
+        onView(withDrawable(close)).check(matches(isDisplayed()));
+        onView(withDrawable(close)).perform(click());
         // Check that icon is argument for single call to click listener.
         verify(supplementalIconClickListener, times(1)).onClick(item);
 
@@ -439,18 +447,21 @@
 
         CarUiContentListItem item = new CarUiContentListItem(
                 CarUiContentListItem.Action.ICON);
-        item.setSupplementalIcon(mActivity.getDrawable(R.drawable.car_ui_icon_close));
+        Drawable close = mActivity.getDrawable(R.drawable.car_ui_icon_close);
+
+        item.setSupplementalIcon(close);
         item.setOnItemClickedListener(mockedItemOnClickListener);
-        item.setTitle("Test item with listener");
+        String title = "Test item with listener";
+        item.setTitle(title);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // Clicks anywhere on the icon should invoke listener.
-        onView(withId(R.id.car_ui_list_item_action_container)).perform(click());
+        onView(withDrawable(close)).perform(click());
         verify(mockedItemOnClickListener, times(1)).onClick(item);
     }
 
@@ -464,7 +475,7 @@
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
     }
 
     @Test
@@ -476,27 +487,31 @@
         CarUiContentListItem.OnClickListener mockedIconListener = mock(
                 CarUiContentListItem.OnClickListener.class);
 
+        Drawable close = mActivity.getDrawable(R.drawable.car_ui_icon_close);
+
         CarUiContentListItem item = new CarUiContentListItem(
                 CarUiContentListItem.Action.ICON);
         item.setSupplementalIcon(
-                mActivity.getDrawable(R.drawable.car_ui_icon_close),
+                close,
                 mockedIconListener);
         item.setOnItemClickedListener(mockedItemOnClickListener);
-        item.setTitle("Test item with listeners");
+        String title = "Test item with listeners";
+        item.setTitle(title);
         items.add(item);
 
         mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+                () -> mCarUiRecyclerView.setAdapter(CarUi.createListItemAdapter(mActivity, items)));
 
-        onView(withId(R.id.car_ui_list_item_title)).check(matches(isDisplayed()));
+        onView(withText(title)).check(matches(isDisplayed()));
 
         // Clicks anywhere on the item (outside of the icon) should only invoke the item click
         // listener.
-        onView(withId(R.id.car_ui_list_item_title)).perform(click());
+        onView(withText(title)).perform(click());
         verify(mockedItemOnClickListener, times(1)).onClick(item);
 
         // Clicks anywhere on the icon should invoke both listeners.
-        onView(withId(R.id.car_ui_list_item_action_container)).perform(click());
+        onView(withDrawable(close)).check(matches(isDisplayed()));
+        onView(withDrawable(close)).perform(click());
         verify(mockedItemOnClickListener, times(1)).onClick(item);
         verify(mockedIconListener, times(1)).onClick(item);
     }
@@ -616,293 +631,6 @@
                 () -> mCarUiRecyclerView.setAdapter(adapter));
     }
 
-    @Test
-    public void testTextTruncation_twoShortLines() {
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText.Builder("Short text string").setMaxLines(2).build());
-        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for no manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(not(withText(containsString(ELLIPSIS)))));
-    }
-
-    @Test
-    public void testTextTruncation_oneLongOneShort_withMaxLines() {
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(2).build());
-        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(ELLIPSIS))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(3, bodyTextView.getLineCount());
-    }
-
-    @Test
-    public void testTextTruncation_oneLongOneShort_noMaxLines() {
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(Integer.MAX_VALUE).build());
-        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for no manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(not(withText(containsString(ELLIPSIS)))));
-    }
-
-    @Test
-    public void testTextTruncation_twoLong_withMaxLines() {
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(3).build());
-        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(3).build());
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(ELLIPSIS))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(6, bodyTextView.getLineCount());
-    }
-
-    @Test
-    public void testTitleTextTruncation_withMaxLines() {
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setTitle(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(2).build());
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_title)).check(
-                matches(withText(containsString(ELLIPSIS))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_title);
-        assertEquals(2, bodyTextView.getLineCount());
-    }
-
-    @Test
-    public void testTextTruncation_twoLong_differentMaxLines() {
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText(LONG_CHAR_SEQUENCE, 1));
-        lines.add(new CarUiText(LONG_CHAR_SEQUENCE, 4));
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(ELLIPSIS))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(5, bodyTextView.getLineCount());
-    }
-
-    @Test
-    public void testMultipleBodyTextLines() {
-        CharSequence line1 = "First short string";
-        CharSequence line2 = "Second short string";
-        CharSequence line3 = "Third short string";
-
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText.Builder(line1).build());
-        lines.add(new CarUiText.Builder(line2).build());
-        lines.add(new CarUiText.Builder(line3).build());
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        String expectedText = line1 + "\n" + line2 + "\n" + line3;
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(expectedText))));
-    }
-
-    @Test
-    public void testBodyTextSpans() {
-        int color = ContextCompat.getColor(mCarUiRecyclerView.getContext(),
-                R.color.car_ui_color_accent);
-
-        Spannable line1 = new SpannableString("This text contains color");
-        line1.setSpan(new ForegroundColorSpan(color), 19, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
-        List<CarUiText> lines = new ArrayList<>();
-        lines.add(new CarUiText(line1, Integer.MAX_VALUE));
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isDisplayed()));
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(line1, bodyTextView.getText());
-    }
-
-    @Test
-    public void testTextWithLineBreak() {
-        List<CarUiText> lines = new ArrayList<>();
-        String firstTwoLines = "This is first line\nThis is the second line";
-        String thirdLine = "\nThis is the third line";
-        lines.add(new CarUiText(firstTwoLines + thirdLine, 2));
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(lines);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(withText(firstTwoLines)));
-        onView(withId(R.id.car_ui_list_item_body)).check(matches(not(withText(thirdLine))));
-    }
-
-    @Test
-    public void testTextVariants() {
-        List<CharSequence> variants = new ArrayList<>();
-        variants.add(LONG_CHAR_SEQUENCE);
-        variants.add("Short string");
-        CarUiText text = new CarUiText(variants, 1);
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(text);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for no manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(not(withText(containsString(ELLIPSIS)))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(1, bodyTextView.getLineCount());
-    }
-
-    @Test
-    public void testTextVariants_withCharLimit() {
-        List<CharSequence> variants = new ArrayList<>();
-        variants.add("Long string");
-        variants.add("Short");
-        CarUiText text = new CarUiText(variants, 1, 5);
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(text);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for no manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(not(withText(containsString(ELLIPSIS)))));
-
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString("Short"))));
-    }
-
-    @Test
-    public void testTextVariants_withCharLimitNoMaxLines() {
-        List<CharSequence> variants = new ArrayList<>();
-        variants.add("Long string");
-        variants.add("Short");
-        CarUiText text = new CarUiText(variants, Integer.MAX_VALUE, 5);
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(text);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for no manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(not(withText(containsString(ELLIPSIS)))));
-
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString("Short"))));
-    }
-
-    @Test
-    public void testTextVariants_noFit() {
-        List<CharSequence> variants = new ArrayList<>();
-        String marker = "MARKING AS PREFERRED VARIANT";
-        variants.add(marker + LONG_CHAR_SEQUENCE);
-        variants.add(LONG_CHAR_SEQUENCE);
-        variants.add(LONG_CHAR_SEQUENCE);
-        CarUiText text = new CarUiText(variants, 2);
-
-        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
-        item.setBody(text);
-        List<CarUiListItem> items = new ArrayList<>();
-        items.add(item);
-
-        mCarUiRecyclerView.post(
-                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
-
-        // Check for manual truncation ellipsis.
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(ELLIPSIS))));
-
-        TextView bodyTextView = mCarUiRecyclerView.requireViewById(R.id.car_ui_list_item_body);
-        assertEquals(2, bodyTextView.getLineCount());
-
-        onView(withId(R.id.car_ui_list_item_body)).check(
-                matches(withText(containsString(marker))));
-    }
-
     @Test()
     public void testListItemAdapter_getCount() {
         List<CarUiRadioButtonListItem> items = new ArrayList<>();
@@ -929,26 +657,25 @@
         onView(withText(itemOneTitle)).check(matches(isDisplayed()));
         onView(withText(itemTwoTitle)).check(matches(isDisplayed()));
         onView(withText(itemThreeTitle)).check(matches(isDisplayed()));
-
         assertEquals(adapter.getItemCount(), 3);
 
         adapter.setMaxItems(2);
-
         assertEquals(adapter.getItemCount(), 2);
     }
 
+    @SuppressWarnings("AssertThrowsMultipleStatements")
     @Test
     public void testUnknownCarUiListItemType_throwsException() {
         List<CarUiListItem> items = new ArrayList<>();
-
         CarUiListItem item = new UnknownCarUiListItem();
         items.add(item);
-
-        CarUiListItemAdapter adapter = new CarUiListItemAdapter(items);
-
         assertThrows("Unknown view type.", IllegalStateException.class,
-                () -> adapter.getItemViewType(0));
+                () -> {
+                    RecyclerView.Adapter adapter = CarUi.createListItemAdapter(mActivity, items);
+                    adapter.getItemViewType(0);
+                });
     }
 
-    private static class UnknownCarUiListItem extends CarUiListItem {}
+    private static class UnknownCarUiListItem extends CarUiListItem {
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewDividerTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewDividerTest.java
new file mode 100644
index 0000000..024ba27
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewDividerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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.ui.recyclerview;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.car.ui.TestActivity;
+import com.android.car.ui.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.car.ui.recyclerview.decorations.grid.GridDividerItemDecoration}
+ * and {@link com.android.car.ui.recyclerview.decorations.linear.LinearDividerItemDecoration}.
+ */
+public class CarUiRecyclerViewDividerTest {
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityRule =
+            new ActivityScenarioRule<>(TestActivity.class);
+
+    @Before
+    public void setUp() {
+        mActivityRule.getScenario().onActivity(activity ->
+                activity.setContentView(R.layout.car_ui_recycler_view_dividers_test_activity));
+    }
+
+    @Test
+    public void test_dividers_linear() {
+        mActivityRule.getScenario().onActivity(activity -> {
+            CarUiRecyclerView recyclerView = activity.findViewById(R.id.list);
+            recyclerView.setAdapter(new TestAdapter());
+            recyclerView.setLayoutStyle(new CarUiLinearLayoutStyle());
+        });
+        onView(withText("Sample item #0")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_dividers_grid() {
+        mActivityRule.getScenario().onActivity(activity -> {
+            CarUiRecyclerView recyclerView = activity.findViewById(R.id.list);
+            recyclerView.setAdapter(new TestAdapter());
+            CarUiGridLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
+            layoutStyle.setSpanCount(3);
+            recyclerView.setLayoutStyle(layoutStyle);
+        });
+        onView(withText("Sample item #0")).check(matches(isDisplayed()));
+    }
+
+    /**
+     * A test adapter that handles inflating test views and binding data to it.
+     */
+    private static class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+        protected final List<String> mData;
+
+        TestAdapter() {
+            int itemCount = 50;
+            mData = new ArrayList<>(itemCount);
+
+            for (int i = 0; i < itemCount; i++) {
+                mData.add("Sample item #" + i);
+            }
+        }
+
+        @NonNull
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new TestViewHolder(LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.test_car_ui_recycler_view_list_item, parent, false));
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
+            holder.bind(mData.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mData.size();
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
index 215c043..8257cad 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/CarUiRecyclerViewTest.java
@@ -32,8 +32,8 @@
 import static androidx.test.espresso.assertion.PositionAssertions.isTopAlignedWith;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
 import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
 import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@@ -41,11 +41,14 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.actions.CarUiRecyclerViewActions.scrollToPosition;
 import static com.android.car.ui.actions.LowLevelActions.performDrag;
 import static com.android.car.ui.actions.LowLevelActions.pressAndHold;
 import static com.android.car.ui.actions.LowLevelActions.release;
 import static com.android.car.ui.actions.LowLevelActions.touchDownAndUp;
 import static com.android.car.ui.actions.ViewActions.waitForView;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 import static com.android.car.ui.recyclerview.CarUiRecyclerView.ItemCap.UNLIMITED;
 import static com.android.car.ui.utils.ViewUtils.setRotaryScrollEnabled;
 
@@ -73,8 +76,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.lang.Math.abs;
 import static java.lang.Math.max;
 
+import android.annotation.TargetApi;
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.res.Resources;
@@ -103,6 +108,8 @@
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.TestActivity;
+import com.android.car.ui.actions.LowLevelActions;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.recyclerview.decorations.grid.GridDividerItemDecoration;
 import com.android.car.ui.test.R;
 import com.android.car.ui.utils.CarUxRestrictionsUtil;
@@ -111,18 +118,30 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
 /**
  * Unit tests for {@link CarUiRecyclerView}.
  */
+@TargetApi(MIN_TARGET_API)
+@RunWith(Parameterized.class)
 public class CarUiRecyclerViewTest {
 
+    @Parameterized.Parameters
+    public static Object[] data() {
+        // It's important to do no plugin first, so that the plugin will
+        // still be enabled when this test finishes
+        return new Object[]{false, true};
+    }
+
     @Rule
     public ActivityScenarioRule<TestActivity> mActivityRule =
             new ActivityScenarioRule<>(TestActivity.class);
@@ -133,8 +152,20 @@
     private Context mTestableContext;
     private Resources mTestableResources;
 
+
+    private Context mPluginContext;
+    private final boolean mIsPluginEnabled;
+
+    public CarUiRecyclerViewTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
+        mIsPluginEnabled = pluginEnabled;
+    }
+
     @Before
     public void setUp() {
+        if (mIsPluginEnabled) {
+            mPluginContext = PluginFactorySingleton.getPluginContext();
+        }
         mScenario = mActivityRule.getScenario();
         mScenario.onActivity(activity -> {
             mActivity = activity;
@@ -150,51 +181,118 @@
         for (IdlingResource idlingResource : IdlingRegistry.getInstance().getResources()) {
             IdlingRegistry.getInstance().unregister(idlingResource);
         }
-    }
 
-    @Test
-    public void testIsScrollbarPresent_scrollbarEnabled() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-        ViewGroup container = mActivity.findViewById(R.id.test_container);
-        container.post(() -> {
-            container.addView(carUiRecyclerView);
-            carUiRecyclerView.setAdapter(new TestAdapter(100));
-        });
-
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+        LowLevelActions.tearDown();
     }
 
     @Test
     public void testIsScrollbarPresent_scrollbarDisabled() {
-        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(doesNotExist());
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
+    }
+
+    @Test
+    public void testSmallRecyclerView_scrollbarDisabled() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
+                null, R.styleable.CarUiRecyclerView));
+        doReturn(typedArray).when(mTestableContext).obtainStyledAttributes(
+                any(),
+                eq(R.styleable.CarUiRecyclerView),
+                anyInt(),
+                anyInt());
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
+                anyInt())).thenReturn(0); // Small size
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(new TestAdapter(100));
+        });
+
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
+    }
+
+    @Test
+    public void testMediumRecyclerView_scrollbarDisabled() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
+                null, R.styleable.CarUiRecyclerView));
+        doReturn(typedArray).when(mTestableContext).obtainStyledAttributes(
+                any(),
+                eq(R.styleable.CarUiRecyclerView),
+                anyInt(),
+                anyInt());
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
+                anyInt())).thenReturn(1); // Medium size
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(new TestAdapter(100));
+        });
+
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
+    }
+
+    @Test
+    public void testLargeRecyclerView_scrollbarDisabled() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
+                null, R.styleable.CarUiRecyclerView));
+        doReturn(typedArray).when(mTestableContext).obtainStyledAttributes(
+                any(),
+                eq(R.styleable.CarUiRecyclerView),
+                anyInt(),
+                anyInt());
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
+                anyInt())).thenReturn(2); // Large size
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(new TestAdapter(100));
+        });
+
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
     }
 
     @Test
     public void testPadding() {
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         int padding = 100;
-        carUiRecyclerView.setPadding(padding, 0, padding, 0);
+        carUiRecyclerView.setPadding(padding, padding, padding, padding);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
         assertEquals(padding, carUiRecyclerView.getPaddingLeft());
         assertEquals(padding, carUiRecyclerView.getPaddingRight());
+        assertEquals(padding, carUiRecyclerView.getPaddingTop());
+        assertEquals(padding, carUiRecyclerView.getPaddingBottom());
     }
 
     @Test
     public void testGridLayout() {
+        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.GRID, 1);
+
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
                 null, R.styleable.CarUiRecyclerView));
 
@@ -208,18 +306,15 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_numOfColumns), anyInt()))
                 .thenReturn(3);
 
-        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
-        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.GRID, 1);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(4);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        assertTrue(carUiRecyclerView.getLayoutManager() instanceof GridLayoutManager);
+        assertTrue(carUiRecyclerView.getLayoutStyle() instanceof CarUiGridLayoutStyle);
 
         // Check that all items in the first row are top-aligned.
         onView(withText(adapter.getItemText(0))).check(
@@ -244,6 +339,9 @@
 
     @Test
     public void testLinearLayout() {
+        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
+        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR, 0);
+
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
                 null, R.styleable.CarUiRecyclerView));
 
@@ -255,18 +353,15 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_layoutStyle), anyInt()))
                 .thenReturn(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR);
 
-        // Ensure the CarUiRecyclerViewLayout constant matches the styleable attribute enum value
-        assertEquals(CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR, 0);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(4);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        assertTrue(carUiRecyclerView.getLayoutManager() instanceof LinearLayoutManager);
+        assertTrue(carUiRecyclerView.getLayoutStyle() instanceof CarUiLinearLayoutStyle);
 
         // Check that item views are laid out linearly.
         onView(withText(adapter.getItemText(0))).check(
@@ -278,7 +373,73 @@
     }
 
     @Test
+    public void testStartAtFirstPosition() {
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        TestAdapter adapter = new TestAdapter(100);
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        // Check that the first item is completely displayed.
+        onView(withText(adapter.getItemText(0))).check(matches(isCompletelyDisplayed()));
+        assertEquals(0, carUiRecyclerView.findFirstCompletelyVisibleItemPosition());
+    }
+
+    @Test
+    public void testPositionAfterPadding() {
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        TestAdapter adapter = new TestAdapter(100);
+        int testPosition = 40;
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+            carUiRecyclerView.scrollToPosition(testPosition);
+        });
+
+        // Check that the scrolled to item is completely displayed.
+        onView(withText(adapter.getItemText(testPosition))).check(matches(isCompletelyDisplayed()));
+        assertEquals(testPosition, carUiRecyclerView.findFirstCompletelyVisibleItemPosition());
+
+        int padding = 150;
+        container.post(() -> carUiRecyclerView.setPadding(padding, padding, padding, padding));
+
+        // Check that the scrolled to item is completely displayed.
+        onView(withText(adapter.getItemText(testPosition))).check(matches(isCompletelyDisplayed()));
+        assertEquals(testPosition, carUiRecyclerView.findFirstCompletelyVisibleItemPosition());
+    }
+
+    @Test
+    public void testPositionAfterPaddingRelative() {
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        TestAdapter adapter = new TestAdapter(100);
+        int testPosition = 40;
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+            carUiRecyclerView.scrollToPosition(testPosition);
+        });
+
+        // Check that the scrolled to item is completely displayed.
+        onView(withText(adapter.getItemText(testPosition))).check(matches(isCompletelyDisplayed()));
+        assertEquals(testPosition, carUiRecyclerView.findFirstCompletelyVisibleItemPosition());
+
+        int padding = 150;
+        container.post(
+                () -> carUiRecyclerView.setPaddingRelative(padding, padding, padding, padding));
+
+        // Check that the scrolled to item is completely displayed.
+        onView(withText(adapter.getItemText(testPosition))).check(matches(isCompletelyDisplayed()));
+        assertEquals(testPosition, carUiRecyclerView.findFirstCompletelyVisibleItemPosition());
+    }
+
+    @Test
     public void testLayoutManagerSetInXml() {
+        if (mIsPluginEnabled) return;
+
         // Inflate activity where a LayoutManger is set for a CarUiRecyclerView through a
         // styleable attribute.
         mActivity.runOnUiThread(
@@ -290,7 +451,7 @@
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
         TestAdapter adapter = new TestAdapter(3);
         mActivity.runOnUiThread(() -> {
-            setRotaryScrollEnabled(carUiRecyclerView, /* isVertical= */ true);
+            setRotaryScrollEnabled(carUiRecyclerView.getView(), /* isVertical= */ true);
             carUiRecyclerView.setAdapter(adapter);
             carUiRecyclerView.setVisibility(View.VISIBLE);
         });
@@ -305,6 +466,8 @@
 
     @Test
     public void testSetLayoutManager_shouldUpdateItemDecorations() {
+        if (mIsPluginEnabled) return;
+
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
                 null, R.styleable.CarUiRecyclerView));
 
@@ -320,23 +483,29 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_numOfColumns), anyInt()))
                 .thenReturn(3);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(4);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        assertTrue(carUiRecyclerView.getLayoutManager() instanceof GridLayoutManager);
-        assertEquals(carUiRecyclerView.getItemDecorationCount(), 3);
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertTrue(carUiRecyclerView.getLayoutStyle() instanceof CarUiGridLayoutStyle);
+        assertEquals(carUiRecyclerView.getItemDecorationCount(), 1);
         assertTrue(carUiRecyclerView.getItemDecorationAt(0) instanceof GridDividerItemDecoration);
 
-        carUiRecyclerView.setLayoutManager(new LinearLayoutManager(mTestableContext));
+        mActivity.runOnUiThread(() ->
+                carUiRecyclerView.setLayoutStyle(new CarUiLinearLayoutStyle()));
 
-        assertTrue(carUiRecyclerView.getLayoutManager() instanceof LinearLayoutManager);
-        assertEquals(carUiRecyclerView.getItemDecorationCount(), 3);
-        assertFalse(carUiRecyclerView.getItemDecorationAt(0) instanceof GridDividerItemDecoration);
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertTrue(carUiRecyclerView.getLayoutStyle() instanceof CarUiLinearLayoutStyle);
+        assertEquals(carUiRecyclerView.getItemDecorationCount(), 1);
+        assertFalse(carUiRecyclerView.getItemDecorationAt(0)
+                instanceof GridDividerItemDecoration);
     }
 
     @Test
@@ -394,9 +563,7 @@
 
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        LinearLayoutManager layoutManager =
-                (LinearLayoutManager) carUiRecyclerView.getLayoutManager();
-        assertEquals(layoutManager.findFirstVisibleItemPosition(), 0);
+        assertEquals(carUiRecyclerView.findFirstVisibleItemPosition(), 0);
     }
 
     @Test
@@ -420,23 +587,19 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        LinearLayoutManager layoutManager =
-                (LinearLayoutManager) carUiRecyclerView.getLayoutManager();
-
-        OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
-        assertEquals(totalSpace, orientationHelper.getTotalSpace());
+        assertEquals(totalSpace, carUiRecyclerView.getTotalSpace());
 
         // Move down one page so there will be sufficient pages for up and downs.
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
-        int topPosition = layoutManager.findFirstVisibleItemPosition();
+        int topPosition = carUiRecyclerView.findFirstVisibleItemPosition();
 
         for (int i = 0; i < 3; i++) {
-            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
-            onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
         }
 
-        assertEquals(layoutManager.findFirstVisibleItemPosition(), topPosition);
+        assertEquals(carUiRecyclerView.findFirstVisibleItemPosition(), topPosition);
     }
 
     @Test
@@ -453,15 +616,12 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        LinearLayoutManager layoutManager =
-                (LinearLayoutManager) carUiRecyclerView.getLayoutManager();
-
         // Press and hold the down button for 2 seconds to scroll the list to bottom.
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(pressAndHold());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(pressAndHold());
         onView(isRoot()).perform(waitForView(withText("Sample item #49"), 3000));
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(release());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(release());
 
-        assertEquals(layoutManager.findLastCompletelyVisibleItemPosition(), 49);
+        assertEquals(carUiRecyclerView.findLastCompletelyVisibleItemPosition(), 49);
     }
 
     @Test
@@ -478,9 +638,9 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        View trackView = mActivity.requireViewById(R.id.car_ui_scrollbar_track);
+        View trackView = mActivity.requireViewById(getId("car_ui_scrollbar_track"));
         // scroll to the middle
-        onView(withId(R.id.car_ui_scrollbar_track)).perform(
+        onView(withId(getId("car_ui_scrollbar_track"))).perform(
                 touchDownAndUp(0f, (trackView.getHeight() / 2f)));
         onView(withText(adapter.getItemText(25))).check(matches(isDisplayed()));
     }
@@ -499,15 +659,15 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        View trackView = mActivity.requireViewById(R.id.car_ui_scrollbar_track);
-        View thumbView = mActivity.requireViewById(R.id.car_ui_scrollbar_thumb);
+        View trackView = mActivity.requireViewById(getId("car_ui_scrollbar_track"));
+        View thumbView = mActivity.requireViewById(getId("car_ui_scrollbar_thumb"));
         // scroll to the end
-        onView(withId(R.id.car_ui_scrollbar_track)).perform(
+        onView(withId(getId("car_ui_scrollbar_track"))).perform(
                 touchDownAndUp(0f, trackView.getHeight() - 1));
         onView(withText(adapter.getItemText(49))).check(matches(isDisplayed()));
 
         // scroll to the start
-        onView(withId(R.id.car_ui_scrollbar_track)).perform(
+        onView(withId(getId("car_ui_scrollbar_track"))).perform(
                 touchDownAndUp(0f, 1));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
     }
@@ -528,12 +688,12 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        View trackView = mActivity.requireViewById(R.id.car_ui_scrollbar_track);
-        View thumbView = mActivity.requireViewById(R.id.car_ui_scrollbar_thumb);
+        View trackView = mActivity.requireViewById(getId("car_ui_scrollbar_track"));
+        View thumbView = mActivity.requireViewById(getId("car_ui_scrollbar_thumb"));
         // if you drag too far in a single step you'll stop selecting the thumb view. Hence, drag
         // 5 units at a time for 200 intervals and stop at the center of the track by limitY.
 
-        onView(withId(R.id.car_ui_scrollbar_track)).perform(
+        onView(withId(getId("car_ui_scrollbar_track"))).perform(
                 performDrag(0f, (thumbView.getHeight() / 2f), 0,
                         5, 200, Float.MAX_VALUE,
                         trackView.getHeight() / 2f));
@@ -555,63 +715,65 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
 
         // Initially page_up button is disabled.
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(not(isEnabled())));
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(not(isEnabled())));
 
         // Moving down, should enable the up bottom.
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(isEnabled()));
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isEnabled()));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(isEnabled()));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(isEnabled()));
 
         // Move back up; this should disable the up button again.
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click()).check(
+        onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click()).check(
                 matches(not(isEnabled())));
     }
 
     @Test
     public void testPageUpScrollsWithoutSnap() {
-        RecyclerView.OnScrollListener scrollListener = mock(RecyclerView.OnScrollListener.class);
+        CarUiRecyclerView.OnScrollListener scrollListener =
+                mock(CarUiRecyclerView.OnScrollListener.class);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
 
         // Scroll down a few pages so that you can perform page up operations.
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Set a mocked scroll listener on the CarUiRecyclerView
         carUiRecyclerView.addOnScrollListener(scrollListener);
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
 
         // Verify that scroll operation only settles on the destination once. This means a single
         // smooth scroll to the destination. If the scroll includes a secondary snap after an
         // initial scroll, this callback will have more than one invocation.
-        verify(scrollListener, times(1)).onScrollStateChanged(carUiRecyclerView,
-                SCROLL_STATE_SETTLING);
+        verify(scrollListener, times(1)).onScrollStateChanged(
+                carUiRecyclerView, SCROLL_STATE_SETTLING);
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
 
         // Make same verification as above for a second page up operation.
-        verify(scrollListener, times(2)).onScrollStateChanged(carUiRecyclerView,
-                SCROLL_STATE_SETTLING);
+        verify(scrollListener, times(2)).onScrollStateChanged(
+                carUiRecyclerView, SCROLL_STATE_SETTLING);
     }
 
     @Test
     public void testPageDownScrollsWithoutSnap() {
-        RecyclerView.OnScrollListener scrollListener = mock(RecyclerView.OnScrollListener.class);
+        CarUiRecyclerView.OnScrollListener scrollListener =
+                mock(CarUiRecyclerView.OnScrollListener.class);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
@@ -620,23 +782,25 @@
         // Set a mocked scroll listener on the CarUiRecyclerView
         carUiRecyclerView.addOnScrollListener(scrollListener);
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Verify that scroll operation only settles on the destination once. This means a single
         // smooth scroll to the destination. If the scroll includes a secondary snap after an
         // initial scroll, this callback will have more than one invocation.
-        verify(scrollListener, times(1)).onScrollStateChanged(carUiRecyclerView,
-                SCROLL_STATE_SETTLING);
+        verify(scrollListener, times(1)).onScrollStateChanged(
+                carUiRecyclerView, SCROLL_STATE_SETTLING);
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Make same verification as above for a second page down operation.
-        verify(scrollListener, times(2)).onScrollStateChanged(carUiRecyclerView,
-                SCROLL_STATE_SETTLING);
+        verify(scrollListener, times(2)).onScrollStateChanged(
+                carUiRecyclerView, SCROLL_STATE_SETTLING);
     }
 
     @Test
     public void testPageDownScrollsOverLongItem() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -658,22 +822,25 @@
 
         OrientationHelper orientationHelper =
                 OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-        int screenHeight = orientationHelper.getTotalSpace();
+        int screenHeight = carUiRecyclerView.getTotalSpace();
+
         // Scroll to a position where long item is partially visible.
         // Scrolling from top, scrollToPosition() aligns the pos-1 item to bottom.
         onView(withId(R.id.list)).perform(scrollToPosition(longItemPosition - 1));
-        // Scroll by half the height of the screen so the long item is partially visible.
-        mActivity.runOnUiThread(() -> carUiRecyclerView.scrollBy(0, screenHeight / 2));
         // This is needed to make sure scroll is finished before looking for the long item.
         onView(withText(adapter.getItemText(longItemPosition - 1))).check(matches(isDisplayed()));
 
+        // Scroll by half the height of the screen so the long item is partially visible.
+        mActivity.runOnUiThread(() -> carUiRecyclerView.scrollBy(0, screenHeight / 2));
+        onView(withText(adapter.getItemText(longItemPosition))).check(matches(isDisplayed()));
+
         // Verify long item is partially shown.
         View longItem = getLongItem(carUiRecyclerView);
         assertThat(
                 orientationHelper.getDecoratedStart(longItem),
                 is(greaterThan(orientationHelper.getStartAfterPadding())));
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Verify long item is snapped to top.
         assertThat(orientationHelper.getDecoratedStart(longItem),
@@ -684,14 +851,14 @@
         // Set a limit to avoid test stuck in non-moving state.
         while (orientationHelper.getDecoratedEnd(longItem)
                 > orientationHelper.getEndAfterPadding()) {
-            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
         }
 
         // Verify long item end is aligned to bottom.
         assertThat(orientationHelper.getDecoratedEnd(longItem),
                 is(equalTo(orientationHelper.getEndAfterPadding())));
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
         // Verify that the long item is no longer visible; Should be on the next child
         assertThat(
                 orientationHelper.getDecoratedStart(longItem),
@@ -700,6 +867,8 @@
 
     @Test
     public void testPageDownScrollsOverLongItemAtTheEnd() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -731,10 +900,10 @@
 
         // 20 is just an arbitrary number to make sure we reach the end of the recyclerview.
         for (int i = 0; i < 20; i++) {
-            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
         }
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(not(isEnabled())));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(not(isEnabled())));
 
         View longItem = getLongItem(carUiRecyclerView);
         // Making sure we've reached end of the recyclerview, after
@@ -745,6 +914,8 @@
 
     @Test
     public void testPageUpScrollsOverLongItem() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -769,6 +940,7 @@
 
         // Scroll to a position just below the long item.
         onView(withId(R.id.list)).perform(scrollToPosition(longItemPosition + 1));
+        onView(withText(adapter.getItemText(longItemPosition + 1))).check(matches(isDisplayed()));
 
         // Verify long item is off-screen.
         View longItem = getLongItem(carUiRecyclerView);
@@ -779,13 +951,13 @@
 
         if (orientationHelper.getStartAfterPadding() - orientationHelper.getDecoratedStart(longItem)
                 < orientationHelper.getTotalSpace()) {
-            onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
             assertThat(orientationHelper.getDecoratedStart(longItem),
                     is(greaterThanOrEqualTo(orientationHelper.getStartAfterPadding())));
         } else {
             int topBeforeClick = orientationHelper.getDecoratedStart(longItem);
 
-            onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
 
             // Verify we scrolled 1 screen
             assertThat(orientationHelper.getStartAfterPadding() - topBeforeClick,
@@ -795,6 +967,8 @@
 
     @Test
     public void testPageDownScrollsOverVeryLongItem() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -821,6 +995,10 @@
         // Scroll to a position where long item is partially visible.
         // Scrolling from top, scrollToPosition() aligns the pos-1 item to bottom.
         onView(withId(R.id.list)).perform(scrollToPosition(longItemPosition - 1));
+
+        onView(withText(adapter.getItemText(longItemPosition - 1)))
+                .check(matches(isDisplayed()));
+
         // Scroll by half the height of the screen so the long item is partially visible.
         mActivity.runOnUiThread(() -> carUiRecyclerView.scrollBy(0, screenHeight / 2));
 
@@ -832,7 +1010,7 @@
                 orientationHelper.getDecoratedStart(longItem),
                 is(greaterThan(orientationHelper.getStartAfterPadding())));
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Verify long item is snapped to top.
         assertThat(orientationHelper.getDecoratedStart(longItem),
@@ -840,7 +1018,7 @@
         assertThat(orientationHelper.getDecoratedEnd(longItem),
                 is(greaterThan(orientationHelper.getEndAfterPadding())));
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Verify long item does not snap to bottom.
         assertThat(orientationHelper.getDecoratedEnd(longItem),
@@ -849,6 +1027,8 @@
 
     @Test
     public void testPageDownScrollsOverVeryLongItemAtTheEnd() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -880,10 +1060,10 @@
 
         // 20 is just an arbitrary number to make sure we reach the end of the recyclerview.
         for (int i = 0; i < 20; i++) {
-            onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+            onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
         }
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(not(isEnabled())));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(not(isEnabled())));
 
         View longItem = getLongItem(carUiRecyclerView);
         // Making sure we've reached end of the recyclerview, after
@@ -899,7 +1079,7 @@
 
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        int itemCount = 25000;
+        int itemCount = 2500;
         TestAdapter adapter = new TestAdapter(itemCount);
 
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
@@ -907,173 +1087,247 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         mActivity.runOnUiThread(() -> carUiRecyclerView.requestLayout());
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         // Check that thumb track maintains minimum height
-        int minThumbViewHeight = mActivity.getResources()
-                .getDimensionPixelOffset(R.dimen.car_ui_scrollbar_min_thumb_height);
-        View thumbView = mActivity.requireViewById(R.id.car_ui_scrollbar_thumb);
-        assertThat(thumbView.getHeight(), is(equalTo(minThumbViewHeight)));
+        int minThumbViewHeight = (int) mActivity.getResources()
+                .getDimension(R.dimen.car_ui_scrollbar_min_thumb_height);
+        View thumbView = mActivity.requireViewById(getId("car_ui_scrollbar_thumb"));
+        // Conversion from DP to pixel sometimes makes these two value differ by 1
+        assertThat(abs(thumbView.getHeight() - minThumbViewHeight), is(lessThanOrEqualTo(1)));
     }
 
     @Test
-    public void testSetPaddingToRecyclerViewContainerWithScrollbar() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-
-        carUiRecyclerView.setPadding(10, 10, 10, 10);
-
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-    }
-
-    @Test
-    public void testSetPaddingToRecyclerViewContainerWithoutScrollbar() {
-        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-
-        carUiRecyclerView.setPadding(10, 10, 10, 10);
-
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(10)));
-    }
-
-    @Test
-    public void testSetPaddingRelativeToRecyclerViewContainerWithScrollbar() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-
-        carUiRecyclerView.setPaddingRelative(10, 10, 10, 10);
-
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-    }
-
-    @Test
-    public void testSetPaddingRelativeToRecyclerViewContainerWithoutScrollbar() {
-        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(0)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(0)));
-
-        carUiRecyclerView.setPaddingRelative(10, 10, 10, 10);
-
-        assertThat(carUiRecyclerView.getPaddingTop(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingBottom(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingStart(), is(equalTo(10)));
-        assertThat(carUiRecyclerView.getPaddingEnd(), is(equalTo(10)));
-    }
-
-    @Test
-    public void testSetAlphaToRecyclerViewWithoutScrollbar() {
-        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
-        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
-
-        carUiRecyclerView.setAlpha(0.5f);
-
-        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(0.5f)));
-    }
-
-    @Test
-    public void testSetAlphaToRecyclerViewWithScrollbar() {
+    public void testRecyclerView_canScrollVertically() {
         mActivity.runOnUiThread(
-                () -> mActivity.setContentView(
-                        R.layout.car_ui_recycler_view_test_activity));
+                () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
         CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
 
-        ViewGroup container = (ViewGroup) carUiRecyclerView.getParent().getParent();
+        // Can't use OrientationHelper here, because it returns 0 when calling getTotalSpace methods
+        // until LayoutManager's onLayoutComplete is called. In this case waiting until the first
+        // item of the list is displayed guarantees that OrientationHelper is initialized properly.
+        int totalSpace = carUiRecyclerView.getHeight()
+                - carUiRecyclerView.getPaddingTop()
+                - carUiRecyclerView.getPaddingBottom();
+        PerfectFitTestAdapter adapter = new PerfectFitTestAdapter(1, totalSpace);
+        mActivity.runOnUiThread(() -> carUiRecyclerView.setAdapter(adapter));
 
-        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
-        assertThat(container.getAlpha(), is(equalTo(1.0f)));
+        IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        carUiRecyclerView.setAlpha(0.5f);
+        assertEquals(totalSpace, carUiRecyclerView.getTotalSpace());
 
-        assertThat(carUiRecyclerView.getAlpha(), is(equalTo(1.0f)));
-        assertThat(container.getAlpha(), is(equalTo(0.5f)));
+        // Both scroll up and down are disabled
+        assertFalse(carUiRecyclerView.getView().canScrollVertically(1));
+        assertFalse(carUiRecyclerView.getView().canScrollVertically(-1));
     }
 
     @Test
-    public void testCallAnimateOnRecyclerViewWithScrollbar() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+    public void testSetPaddingToRecyclerViewContainerWithScrollbar() {
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
+        TestAdapter adapter = new TestAdapter(50);
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
-        container.post(() -> {
-            container.addView(carUiRecyclerView);
-            carUiRecyclerView.setAdapter(new TestAdapter(100));
+        mActivity.runOnUiThread(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
 
-        ViewGroup recyclerViewContainer = (ViewGroup) carUiRecyclerView.getParent().getParent();
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingLeft(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingRight(), is(equalTo(0)));
+        // available space in recyclerview after applying paddings.
+        int screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight(), is(equalTo(screenHeight)));
 
-        assertThat(carUiRecyclerView.animate(), is(equalTo(recyclerViewContainer.animate())));
+        View scrollbar = mActivity.findViewById(getId("car_ui_scroll_bar"));
+        assertThat(scrollbar.getPaddingTop(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingLeft(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingRight(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingBottom(), is(equalTo(0)));
+
+        mActivity.runOnUiThread(() -> carUiRecyclerView.setPadding(20, 150, 30, 170));
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertThat(carUiRecyclerView.getView().getPaddingLeft(), is(equalTo(20)));
+        assertThat(carUiRecyclerView.getView().getPaddingRight(), is(equalTo(30)));
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(150)));
+        // available space in recyclerview after applying paddings.
+        screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight() - 150 - 170, is(equalTo(screenHeight)));
+
+        assertThat(scrollbar.getPaddingTop(), is(equalTo(150)));
+        assertThat(scrollbar.getPaddingLeft(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingRight(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingBottom(), is(equalTo(170)));
+    }
+
+    @Test
+    public void testSetPaddingToRecyclerViewContainerWithoutScrollbar() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        TestAdapter adapter = new TestAdapter(5);
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        mActivity.runOnUiThread(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
+
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingLeft(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingRight(), is(equalTo(0)));
+        // available space in recyclerview after applying paddings.
+        int screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight(), is(equalTo(screenHeight)));
+
+        mActivity.runOnUiThread(() -> carUiRecyclerView.setPadding(20, 150, 30, 170));
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertThat(carUiRecyclerView.getView().getPaddingLeft(), is(equalTo(20)));
+        assertThat(carUiRecyclerView.getView().getPaddingRight(), is(equalTo(30)));
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(150)));
+        // available space in recyclerview after applying paddings.
+        screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight() - 150 - 170, is(equalTo(screenHeight)));
+    }
+
+    @Test
+    public void testSetPaddingRelativeToRecyclerViewContainerWithScrollbar() {
+        if (isScrollbarEnabledNotAsExpected(true)) return;
+
+        TestAdapter adapter = new TestAdapter(50);
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        mActivity.runOnUiThread(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
+
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingStart(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingEnd(), is(equalTo(0)));
+        // available space in recyclerview after applying paddings.
+        int screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight(), is(equalTo(screenHeight)));
+
+        View scrollbar = mActivity.findViewById(getId("car_ui_scroll_bar"));
+        assertThat(scrollbar.getPaddingTop(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingStart(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingEnd(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingBottom(), is(equalTo(0)));
+
+        mActivity.runOnUiThread(() -> {
+            carUiRecyclerView.setPaddingRelative(20, 150, 30, 170);
+        });
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertThat(carUiRecyclerView.getView().getPaddingStart(), is(equalTo(20)));
+        assertThat(carUiRecyclerView.getView().getPaddingEnd(), is(equalTo(30)));
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(150)));
+        // available space in recyclerview after applying paddings.
+        screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight() - 150 - 170, is(equalTo(screenHeight)));
+
+        assertThat(scrollbar.getPaddingTop(), is(equalTo(150)));
+        assertThat(scrollbar.getPaddingStart(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingEnd(), is(equalTo(0)));
+        assertThat(scrollbar.getPaddingBottom(), is(equalTo(170)));
+    }
+
+    @Test
+    public void testSetPaddingRelativeToRecyclerViewContainerWithoutScrollbar() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        TestAdapter adapter = new TestAdapter(5);
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        mActivity.runOnUiThread(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
+
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingStart(), is(equalTo(0)));
+        assertThat(carUiRecyclerView.getView().getPaddingEnd(), is(equalTo(0)));
+        // available space in recyclerview after applying paddings.
+        int screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight(), is(equalTo(screenHeight)));
+
+        mActivity.runOnUiThread(() -> carUiRecyclerView.setPaddingRelative(20, 150, 30, 170));
+
+        onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
+
+        assertThat(carUiRecyclerView.getView().getPaddingStart(), is(equalTo(20)));
+        assertThat(carUiRecyclerView.getView().getPaddingEnd(), is(equalTo(30)));
+        assertThat(carUiRecyclerView.getStartAfterPadding(), is(equalTo(150)));
+        // available space in recyclerview after applying paddings.
+        screenHeight = carUiRecyclerView.getTotalSpace();
+        assertThat(carUiRecyclerView.getHeight() - 150 - 170, is(equalTo(screenHeight)));
+    }
+
+    @Test
+    public void testSetAlphaToRecyclerView() {
+        if (isScrollbarEnabledNotAsExpected(false)) return;
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+
+        assertThat(carUiRecyclerView.getView().getAlpha(), is(equalTo(1.0f)));
+
+        carUiRecyclerView.setAlpha(0.5f);
+
+        assertThat(carUiRecyclerView.getView().getAlpha(), is(equalTo(0.5f)));
     }
 
     @Test
     public void testScrollbarVisibility_tooSmallHeight() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
         // Set to anything less than 2 * (minTouchSize + margin)
         // minTouchSize = R.dimen.car_ui_touch_target_size
         // margin is button up top margin or button down bottom margin
         int recyclerviewHeight = 1;
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1, recyclerviewHeight);
-            container.addView(carUiRecyclerView, lp);
+            container.addView(carUiRecyclerView.getView(), lp);
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(not(isDisplayed())));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(not(isDisplayed())));
 
-        OrientationHelper orientationHelper =
-                OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-        int screenHeight = orientationHelper.getTotalSpace();
-
-        assertEquals(recyclerviewHeight, screenHeight);
+        assertEquals(recyclerviewHeight, carUiRecyclerView.getTotalSpace());
     }
 
     @Test
     public void testScrollbarVisibility_justEnoughToShowOnlyButtons() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
         // R.dimen.car_ui_touch_target_size
         float minTouchSize = mTestableResources.getDimension(R.dimen.car_ui_touch_target_size);
@@ -1083,31 +1337,28 @@
         // Set to 2 * (minTouchSize + margin)
         int recyclerviewHeight = 2 * (int) (minTouchSize + margin);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1, recyclerviewHeight);
-            container.addView(carUiRecyclerView, lp);
+            container.addView(carUiRecyclerView.getView(), lp);
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_thumb)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_scrollbar_track)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_thumb"))).check(matches(not(isDisplayed())));
+        onView(withId(getId("car_ui_scrollbar_track"))).check(matches(not(isDisplayed())));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(isDisplayed()));
 
-        OrientationHelper orientationHelper =
-                OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-        int screenHeight = orientationHelper.getTotalSpace();
+        int screenHeight = carUiRecyclerView.getTotalSpace();
 
         assertEquals(recyclerviewHeight, screenHeight);
     }
 
     @Test
     public void testScrollbarVisibility_enoughToShowEverything() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
         int minTouchSize = (int) mTestableResources.getDimension(R.dimen.car_ui_touch_target_size);
         int mScrollbarThumbMinHeight = (int) mTestableResources
@@ -1120,54 +1371,49 @@
         // Set to anything greater or equal to
         // 2 * minTouchSize + max(minTouchSize, mScrollbarThumbMinHeight) + 2 * margin
         int recyclerviewHeight =
-                2 *  minTouchSize
-                + max(minTouchSize, mScrollbarThumbMinHeight)
-                + 2 * margin + trackMargin;
+                2 * minTouchSize
+                        + max(minTouchSize, mScrollbarThumbMinHeight)
+                        + 2 * margin + trackMargin;
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         container.post(() -> {
             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1, recyclerviewHeight);
-            container.addView(carUiRecyclerView, lp);
+            container.addView(carUiRecyclerView.getView(), lp);
             carUiRecyclerView.setAdapter(new TestAdapter(100));
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_thumb)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_track)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(isDisplayed()));
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_thumb"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_track"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(isDisplayed()));
 
-        OrientationHelper orientationHelper =
-                OrientationHelper.createVerticalHelper(carUiRecyclerView.getLayoutManager());
-        int screenHeight = orientationHelper.getTotalSpace();
-
-        assertEquals(recyclerviewHeight, screenHeight);
+        assertEquals(recyclerviewHeight, carUiRecyclerView.getTotalSpace());
     }
 
     @Test
     public void testDefaultSize_noScrollbar() {
-        doReturn(false).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (isScrollbarEnabledNotAsExpected(false)) return;
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         int listId = View.generateViewId();
         TestAdapter adapter = new TestAdapter(50);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setId(listId);
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(doesNotExist());
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExist());
         onView(withText(adapter.getItemText(0))).check(isLeftAlignedWith(withId(listId)));
         onView(withText(adapter.getItemText(0))).check(isRightAlignedWith(withId(listId)));
     }
 
     @Test
     public void testLargeSize_withScrollbar() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
                 null, R.styleable.CarUiRecyclerView));
@@ -1180,26 +1426,26 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
                 anyInt())).thenReturn(2); // Large size
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         int listId = View.generateViewId();
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(50);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setId(listId);
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
         onView(withText(adapter.getItemText(0))).check(
-                isCompletelyRightOf(withId(R.id.car_ui_scroll_bar)));
+                isCompletelyRightOf(withId(getId("car_ui_scroll_bar"))));
         onView(withText(adapter.getItemText(0))).check(
                 matches(not(isRightAlignedWith(withId(listId)))));
     }
 
     @Test
     public void testMediumSize_withScrollbar() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (isScrollbarEnabledNotAsExpected(true)) return;
 
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
                 null, R.styleable.CarUiRecyclerView));
@@ -1212,24 +1458,54 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
                 anyInt())).thenReturn(1); // Medium size
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         int listId = View.generateViewId();
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(50);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setId(listId);
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(matches(isDisplayed()));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
         onView(withText(adapter.getItemText(0))).check(
-                isCompletelyRightOf(withId(R.id.car_ui_scroll_bar)));
+                isCompletelyRightOf(withId(getId("car_ui_scroll_bar"))));
         onView(withText(adapter.getItemText(0))).check(isRightAlignedWith(withId(listId)));
     }
 
     @Test
-    public void testSmallSize_withScrollbar() {
+    public void testSmallSize_oneItem() {
+        if (isScrollbarEnabledNotAsExpected(true)) return;
+
+        TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
+                null, R.styleable.CarUiRecyclerView));
+
+        doReturn(typedArray).when(mTestableContext).obtainStyledAttributes(
+                any(),
+                eq(R.styleable.CarUiRecyclerView),
+                anyInt(),
+                anyInt());
+        when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
+                anyInt())).thenReturn(0); // Small size
+
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
+        int listId = View.generateViewId();
+        ViewGroup container = mActivity.findViewById(R.id.test_container);
+        TestAdapter adapter = new TestAdapter(1);
+        container.post(() -> {
+            container.addView(carUiRecyclerView.getView());
+            carUiRecyclerView.setId(listId);
+            carUiRecyclerView.setAdapter(adapter);
+        });
+
+        onView(withId(getId("car_ui_scroll_bar"))).check(doesNotExistOrIsNotDisplayed());
+        onView(withText(adapter.getItemText(0))).check(isLeftAlignedWith(withId(listId)));
+        onView(withText(adapter.getItemText(0))).check(isRightAlignedWith(withId(listId)));
+    }
+
+    @Test
+    public void testSmallSize_multipleItem() {
         doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
 
         TypedArray typedArray = spy(mActivity.getBaseContext().obtainStyledAttributes(
@@ -1243,23 +1519,26 @@
         when(typedArray.getInt(eq(R.styleable.CarUiRecyclerView_carUiSize),
                 anyInt())).thenReturn(0); // Small size
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         int listId = View.generateViewId();
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(50);
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setId(listId);
             carUiRecyclerView.setAdapter(adapter);
         });
 
-        onView(withId(R.id.car_ui_scroll_bar)).check(doesNotExist());
-        onView(withText(adapter.getItemText(0))).check(isLeftAlignedWith(withId(listId)));
+        onView(withId(getId("car_ui_scroll_bar"))).check(matches(isDisplayed()));
+        onView(withText(adapter.getItemText(0))).check(
+                isCompletelyRightOf(withId(getId("car_ui_scroll_bar"))));
         onView(withText(adapter.getItemText(0))).check(isRightAlignedWith(withId(listId)));
     }
 
     @Test
     public void testSameSizeItems_estimateNextPositionDiffForScrollDistance() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1273,12 +1552,13 @@
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
+        // TODO:
         LayoutManager layoutManager = carUiRecyclerView.getLayoutManager();
         OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
 
         int firstViewHeight = layoutManager.getChildAt(0).getHeight();
         int itemsToScroll = 10;
-        CarUiSnapHelper snapHelper  = new CarUiSnapHelper(mActivity);
+        CarUiSnapHelper snapHelper = new CarUiSnapHelper(mActivity);
         // Get an estimate of how many items CarUiSnaphelpwer says we need to scroll. The scroll
         // distance is set to 10 * height of the first item. Since all items have the items have
         // the same height, we're expecting to get exactly 10 back from CarUiSnapHelper.
@@ -1290,6 +1570,8 @@
 
     @Test
     public void testSameSizeItems_estimateNextPositionDiffForScrollDistance_zeroDistance() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1306,11 +1588,12 @@
         LayoutManager layoutManager = carUiRecyclerView.getLayoutManager();
         OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
 
+        // TODO:
         int firstViewHeight = layoutManager.getChildAt(0).getHeight();
         // the scroll distance has to be less than half of the size of the first view so that
         // recyclerview doesn't snap to the next view
         int distantToScroll = (firstViewHeight / 2) - 1;
-        CarUiSnapHelper snapHelper  = new CarUiSnapHelper(mActivity);
+        CarUiSnapHelper snapHelper = new CarUiSnapHelper(mActivity);
         int estimate = snapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
                 distantToScroll);
 
@@ -1319,6 +1602,8 @@
 
     @Test
     public void testSameSizeItems_estimateNextPositionDiffForScrollDistance_zeroHeight() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1340,7 +1625,7 @@
 
         // 10 is an arbitrary number
         int distantToScroll = 10;
-        CarUiSnapHelper snapHelper  = new CarUiSnapHelper(mActivity);
+        CarUiSnapHelper snapHelper = new CarUiSnapHelper(mActivity);
         int estimate = snapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper,
                 distantToScroll);
 
@@ -1349,6 +1634,8 @@
 
     @Test
     public void testSameSizeItems_estimateNextPositionDiffForScrollDistance_zeroItems() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1364,7 +1651,7 @@
 
         LayoutManager layoutManager = carUiRecyclerView.getLayoutManager();
         OrientationHelper orientationHelper = OrientationHelper.createVerticalHelper(layoutManager);
-        CarUiSnapHelper snapHelper  = new CarUiSnapHelper(mActivity);
+        CarUiSnapHelper snapHelper = new CarUiSnapHelper(mActivity);
         int estimate = snapHelper.estimateNextPositionDiffForScrollDistance(orientationHelper, 50);
 
         assertEquals(estimate, 50);
@@ -1372,6 +1659,8 @@
 
     @Test
     public void testEmptyList_calculateScrollDistanceClampToScreenSize() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1389,14 +1678,16 @@
 
         LinearSnapHelper linearSnapHelper = new LinearSnapHelper();
         carUiRecyclerView.setOnFlingListener(null);
-        linearSnapHelper.attachToRecyclerView(carUiRecyclerView);
+        linearSnapHelper.attachToRecyclerView(
+                ((CarUiRecyclerViewImpl) carUiRecyclerView).getRecyclerView());
         // 200 is just an arbitrary number. the intent is to make sure the return value is smaller
         // than the layoutmanager height.
         int[] baseOutDist = linearSnapHelper.calculateScrollDistance(200, -200);
 
         CarUiSnapHelper carUiSnapHelper = new CarUiSnapHelper(mTestableContext);
         carUiRecyclerView.setOnFlingListener(null);
-        carUiSnapHelper.attachToRecyclerView(carUiRecyclerView);
+        carUiSnapHelper.attachToRecyclerView(
+                ((CarUiRecyclerViewImpl) carUiRecyclerView).getRecyclerView());
         int[] outDist = carUiSnapHelper.calculateScrollDistance(200, -200);
 
         assertEquals(outDist[0], baseOutDist[0]);
@@ -1405,6 +1696,8 @@
 
     @Test
     public void testCalculateScrollDistanceClampToScreenSize() {
+        if (mIsPluginEnabled) return;
+
         mActivity.runOnUiThread(
                 () -> mActivity.setContentView(R.layout.car_ui_recycler_view_test_activity));
 
@@ -1424,14 +1717,16 @@
 
         LinearSnapHelper linearSnapHelper = new LinearSnapHelper();
         carUiRecyclerView.setOnFlingListener(null);
-        linearSnapHelper.attachToRecyclerView(carUiRecyclerView);
+        linearSnapHelper.attachToRecyclerView(
+                ((CarUiRecyclerViewImpl) carUiRecyclerView).getRecyclerView());
         // 8000 is just an arbitrary number. the intent is to make sure the return value is bigger
         // than the layoutmanager height.
         int[] baseOutDist = linearSnapHelper.calculateScrollDistance(8000, -8000);
 
         CarUiSnapHelper carUiSnapHelper = new CarUiSnapHelper(mTestableContext);
         carUiRecyclerView.setOnFlingListener(null);
-        carUiSnapHelper.attachToRecyclerView(carUiRecyclerView);
+        carUiSnapHelper.attachToRecyclerView(
+                ((CarUiRecyclerViewImpl) carUiRecyclerView).getRecyclerView());
         int[] outDist = carUiSnapHelper.calculateScrollDistance(8000, -8000);
 
         int lastChildPosition = carUiSnapHelper.isAtEnd(layoutManager)
@@ -1456,66 +1751,60 @@
 
     @Test
     public void testContinuousScrollListenerConstructor_negativeInitialDelay() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (mIsPluginEnabled) return;
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
         doReturn(-1).when(mTestableResources)
-            .getInteger(R.integer.car_ui_scrollbar_longpress_initial_delay);
+                .getInteger(R.integer.car_ui_scrollbar_longpress_initial_delay);
 
-        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
-        ViewGroup container = mActivity.findViewById(R.id.test_container);
-        TestAdapter adapter = new TestAdapter(50);
-
-        container.post(() -> carUiRecyclerView.setAdapter(adapter));
-        container.post(() -> assertThrows("negative intervals are not allowed",
-                IllegalArgumentException.class, () -> container.addView(carUiRecyclerView)));
+        IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+                () -> CarUiRecyclerView.create(mTestableContext));
+        assertEquals("negative intervals are not allowed", ex.getMessage());
     }
 
     @Test
     public void testContinuousScrollListenerConstructor_negativeRepeatInterval() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (mIsPluginEnabled) return;
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
         doReturn(-1).when(mTestableResources)
                 .getInteger(R.integer.car_ui_scrollbar_longpress_repeat_interval);
 
-        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
-        ViewGroup container = mActivity.findViewById(R.id.test_container);
-        TestAdapter adapter = new TestAdapter(50);
-
-        container.post(() -> carUiRecyclerView.setAdapter(adapter));
-        container.post(() -> assertThrows("negative intervals are not allowed",
-                IllegalArgumentException.class, () -> container.addView(carUiRecyclerView)));
+        IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
+                () -> CarUiRecyclerView.create(mTestableContext));
+        assertEquals("negative intervals are not allowed", ex.getMessage());
     }
 
     @Test
     public void testUnknownClass_createScrollBarFromConfig() {
+        if (mIsPluginEnabled) return;
+
         doReturn("random.class").when(mTestableResources)
                 .getString(R.string.car_ui_scrollbar_component);
 
-        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
-        ViewGroup container = mActivity.findViewById(R.id.test_container);
-        TestAdapter adapter = new TestAdapter(50);
-
-        container.post(() -> carUiRecyclerView.setAdapter(adapter));
-        container.post(() -> assertThrows("Error loading scroll bar component: random.class",
-                RuntimeException.class, () -> container.addView(carUiRecyclerView)));
+        RuntimeException ex = assertThrows(RuntimeException.class,
+                () -> CarUiRecyclerView.create(mTestableContext));
+        assertEquals("Error loading scroll bar component: random.class", ex.getMessage());
     }
 
     @Test
     public void testWrongType_createScrollBarFromConfig() {
+        if (mIsPluginEnabled) return;
+
         // Basically return any class that exists but doesn't extend ScrollBar
-        doReturn(CarUiRecyclerViewImpl.class.getName()).when(mTestableResources)
-            .getString(R.string.car_ui_scrollbar_component);
+        doReturn(CarUiRecyclerView.class.getName()).when(mTestableResources)
+                .getString(R.string.car_ui_scrollbar_component);
 
-        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
-        ViewGroup container = mActivity.findViewById(R.id.test_container);
-        TestAdapter adapter = new TestAdapter(50);
-
-        container.post(() -> carUiRecyclerView.setAdapter(adapter));
-        container.post(() -> assertThrows("Error loading scroll bar component: "
-                + CarUiRecyclerViewImpl.class.getName(),
-                RuntimeException.class, () -> container.addView(carUiRecyclerView)));
+        RuntimeException ex = assertThrows(RuntimeException.class,
+                () -> CarUiRecyclerView.create(mTestableContext));
+        assertEquals("Error creating scroll bar component: "
+                + CarUiRecyclerView.class.getName(), ex.getMessage());
     }
 
     @Test
     public void testSetLinearLayoutStyle_setsLayoutManager() {
+        if (mIsPluginEnabled) return;
+
         CarUiLayoutStyle layoutStyle = new CarUiLinearLayoutStyle();
         CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         carUiRecyclerView.setLayoutStyle(layoutStyle);
@@ -1523,7 +1812,7 @@
         TestAdapter adapter = new TestAdapter(50);
 
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
@@ -1538,6 +1827,8 @@
 
     @Test
     public void testSetGridLayoutStyle_setsLayoutManager() {
+        if (mIsPluginEnabled) return;
+
         CarUiLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
         CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         carUiRecyclerView.setLayoutStyle(layoutStyle);
@@ -1545,7 +1836,7 @@
         TestAdapter adapter = new TestAdapter(50);
 
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
@@ -1562,20 +1853,22 @@
 
     @Test
     public void testNegativeSpanCount_setSpanCount() {
-        CarUiLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
-        assertThrows(AssertionError.class, () -> ((CarUiGridLayoutStyle) layoutStyle)
-                .setSpanCount((-1)));
+        CarUiGridLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
+        AssertionError ex = assertThrows(AssertionError.class,
+                () -> layoutStyle.setSpanCount((-1)));
+        assertEquals("Span count must be bigger than 0", ex.getMessage());
     }
 
     @Test
     public void testSetGridLayoutStyle_setsLayoutManagerSpanSizeLookup() {
+
         CarUiGridLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
         // has to bigger than span sizes for all the rows
         layoutStyle.setSpanCount(20);
         SpanSizeLookup spanSizeLookup = new SpanSizeLookup() {
             @Override
             public int getSpanSize(int position) {
-                switch(position) {
+                switch (position) {
                     case 0:
                         return 10;
                     case 1:
@@ -1592,29 +1885,31 @@
         TestAdapter adapter = new TestAdapter(50);
 
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
-        assertTrue(carUiRecyclerView.getLayoutManager() instanceof GridLayoutManager);
-        assertEquals(((GridLayoutManager) carUiRecyclerView.getLayoutManager()).getSpanSizeLookup()
+        assertTrue(carUiRecyclerView.getLayoutStyle() instanceof CarUiGridLayoutStyle);
+        assertEquals(((CarUiGridLayoutStyle) carUiRecyclerView.getLayoutStyle()).getSpanSizeLookup()
                 .getSpanSize(0), spanSizeLookup.getSpanSize(0));
-        assertEquals(((GridLayoutManager) carUiRecyclerView.getLayoutManager()).getSpanSizeLookup()
+        assertEquals(((CarUiGridLayoutStyle) carUiRecyclerView.getLayoutStyle()).getSpanSizeLookup()
                 .getSpanSize(1), spanSizeLookup.getSpanSize(1));
-        assertEquals(((GridLayoutManager) carUiRecyclerView.getLayoutManager()).getSpanSizeLookup()
+        assertEquals(((CarUiGridLayoutStyle) carUiRecyclerView.getLayoutStyle()).getSpanSizeLookup()
                 .getSpanSize(2), spanSizeLookup.getSpanSize(2));
     }
 
     @Test
     public void testCarUiGridLayoutStyle_LayoutManagerFrom() {
+        if (mIsPluginEnabled) return;
+
         GridLayoutManager layoutManager =
                 new GridLayoutManager(mTestableContext, 20, RecyclerView.VERTICAL, true);
         layoutManager.setSpanSizeLookup(new SpanSizeLookup() {
             @Override
             public int getSpanSize(int position) {
-                switch(position) {
+                switch (position) {
                     case 0:
                         return 10;
                     case 1:
@@ -1631,7 +1926,7 @@
         TestAdapter adapter = new TestAdapter(50);
 
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
@@ -1655,7 +1950,9 @@
     @Test
     public void testCarUiGridLayoutStyle_fromLinearLayout_throwsException() {
         LinearLayoutManager layoutManager = new LinearLayoutManager(mTestableContext);
-        assertThrows(AssertionError.class, () -> CarUiGridLayoutStyle.from(layoutManager));
+        AssertionError ex = assertThrows(AssertionError.class,
+                () -> CarUiGridLayoutStyle.from(layoutManager));
+        assertEquals("GridLayoutManager required.", ex.getMessage());
     }
 
     @Test
@@ -1665,6 +1962,8 @@
 
     @Test
     public void testCarUiLinearLayoutStyle_LayoutManagerFrom() {
+        if (mIsPluginEnabled) return;
+
         LinearLayoutManager layoutManager =
                 new LinearLayoutManager(mTestableContext, RecyclerView.VERTICAL, true);
         CarUiLinearLayoutStyle layoutStyle = CarUiLinearLayoutStyle.from(layoutManager);
@@ -1674,7 +1973,7 @@
         TestAdapter adapter = new TestAdapter(50);
 
         container.post(() -> {
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
             carUiRecyclerView.setAdapter(adapter);
         });
 
@@ -1690,7 +1989,9 @@
     @Test
     public void testCarUiLinearLayoutStyle_fromGridLayout_throwsException() {
         NotLinearLayoutManager layoutManager = new NotLinearLayoutManager(mTestableContext);
-        assertThrows(AssertionError.class, () -> CarUiLinearLayoutStyle.from(layoutManager));
+        AssertionError ex = assertThrows(AssertionError.class,
+                () -> CarUiLinearLayoutStyle.from(layoutManager));
+        assertEquals("LinearLayoutManager required.", ex.getMessage());
     }
 
     @Test
@@ -1717,18 +2018,20 @@
 
     @Test
     public void testUxRestriction_withLimitedContent_setsMaxItems() {
+        if (mIsPluginEnabled) return;
+
         CarUxRestrictionsUtil uxRestriction = CarUxRestrictionsUtil.getInstance(mTestableContext);
         CarUxRestrictions restriction = mock(CarUxRestrictions.class);
         when(restriction.getActiveRestrictions()).thenReturn(UX_RESTRICTIONS_LIMIT_CONTENT);
         when(restriction.getMaxCumulativeContentItems()).thenReturn(10);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter.WithItemCap adapter = spy(new TestAdapter.WithItemCap(100));
         container.post(() -> {
             uxRestriction.setUxRestrictions(restriction);
             carUiRecyclerView.setAdapter(adapter);
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -1739,17 +2042,19 @@
 
     @Test
     public void testUxRestriction_withoutLimitedContent_setsUnlimitedMaxItems() {
+        if (mIsPluginEnabled) return;
+
         CarUxRestrictionsUtil uxRestriction = CarUxRestrictionsUtil.getInstance(mTestableContext);
         CarUxRestrictions restriction = mock(CarUxRestrictions.class);
         when(restriction.getMaxCumulativeContentItems()).thenReturn(10);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter.WithItemCap adapter = spy(new TestAdapter.WithItemCap(100));
         container.post(() -> {
             uxRestriction.setUxRestrictions(restriction);
             carUiRecyclerView.setAdapter(adapter);
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -1760,50 +2065,56 @@
 
     @Test
     public void testPageUp_returnsWhen_verticalScrollOffsetIsZero() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (mIsPluginEnabled) return;
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
         doReturn(TestScrollBar.class.getName()).when(mTestableResources)
-            .getString(R.string.car_ui_scrollbar_component);
+                .getString(R.string.car_ui_scrollbar_component);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(100);
+        int listId = View.generateViewId();
         container.post(() -> {
             carUiRecyclerView.setAdapter(adapter);
-            container.addView(carUiRecyclerView);
+            carUiRecyclerView.setId(listId);
+            container.addView(carUiRecyclerView.getView());
         });
 
+        onView(withId(listId)).check(matches(isDisplayed()));
+
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
         onView(withText(adapter.getItemText(0))).check(matches(isDisplayed()));
 
         // Scroll to a position so page up is enabled.
         container.post(() -> carUiRecyclerView.scrollToPosition(20));
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isEnabled()));
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(isEnabled()));
 
-        View v = mActivity.findViewById(R.id.car_ui_scroll_bar);
+        View v = mActivity.findViewById(getId("car_ui_scroll_bar"));
         TestScrollBar sb = (TestScrollBar) v.getTag();
         // We set this to simulate a case where layout manager is null
         sb.mReturnZeroVerticalScrollOffset = true;
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
 
         assertFalse(sb.mScrollWasCalled);
     }
 
     @Test
     public void testPageUp_returnsWhen_layoutManagerIsNull() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (mIsPluginEnabled) return;
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
         doReturn(TestScrollBar.class.getName()).when(mTestableResources)
                 .getString(R.string.car_ui_scrollbar_component);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(100);
         container.post(() -> {
             carUiRecyclerView.setAdapter(adapter);
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -1812,31 +2123,32 @@
         // Scroll to a position so page up is enabled.
         container.post(() -> carUiRecyclerView.scrollToPosition(20));
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).check(matches(isEnabled()));
+        onView(withId(getId("car_ui_scrollbar_page_up"))).check(matches(isEnabled()));
 
-        View v = mActivity.findViewById(R.id.car_ui_scroll_bar);
+        View v = mActivity.findViewById(getId("car_ui_scroll_bar"));
         TestScrollBar sb = (TestScrollBar) v.getTag();
         // We set this to simulate a case where layout manager is null
         sb.mReturnMockLayoutManager = true;
 
-        onView(withId(R.id.car_ui_scrollbar_page_up)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_up"))).perform(click());
 
         assertFalse(sb.mScrollWasCalled);
     }
 
     @Test
     public void testPageDown_returnsWhen_layoutManagerIsNullOrEmpty() {
-        doReturn(true).when(mTestableResources).getBoolean(R.bool.car_ui_scrollbar_enable);
+        if (mIsPluginEnabled) return;
+
+        if (isScrollbarEnabledNotAsExpected(true)) return;
         doReturn(TestScrollBar.class.getName()).when(mTestableResources)
-            .getString(R.string.car_ui_scrollbar_component);
+                .getString(R.string.car_ui_scrollbar_component);
 
-        CarUiRecyclerView carUiRecyclerView = new CarUiRecyclerViewImpl(mTestableContext);
-
+        CarUiRecyclerView carUiRecyclerView = CarUiRecyclerView.create(mTestableContext);
         ViewGroup container = mActivity.findViewById(R.id.test_container);
         TestAdapter adapter = new TestAdapter(100);
         container.post(() -> {
             carUiRecyclerView.setAdapter(adapter);
-            container.addView(carUiRecyclerView);
+            container.addView(carUiRecyclerView.getView());
         });
 
         IdlingRegistry.getInstance().register(new ScrollIdlingResource(carUiRecyclerView));
@@ -1845,16 +2157,16 @@
         // Scroll to a position so page up is enabled.
         container.post(() -> carUiRecyclerView.scrollToPosition(20));
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).check(matches(isEnabled()));
+        onView(withId(getId("car_ui_scrollbar_page_down"))).check(matches(isEnabled()));
 
-        View v = mActivity.findViewById(R.id.car_ui_scroll_bar);
+        View v = mActivity.findViewById(getId("car_ui_scroll_bar"));
         TestScrollBar sb = (TestScrollBar) v.getTag();
         // We set this to simulate a case where layout manager is empty
         sb.mReturnMockLayoutManager = true;
         sb.mMockLayoutManager = spy(carUiRecyclerView.getLayoutManager());
         when(sb.mMockLayoutManager.getChildCount()).thenReturn(0);
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         assertFalse(sb.mScrollWasCalled);
 
@@ -1862,7 +2174,7 @@
         sb.mReturnMockLayoutManager = true;
         sb.mMockLayoutManager = null;
 
-        onView(withId(R.id.car_ui_scrollbar_page_down)).perform(click());
+        onView(withId(getId("car_ui_scrollbar_page_down"))).perform(click());
 
         assertFalse(sb.mScrollWasCalled);
     }
@@ -1875,8 +2187,8 @@
         boolean mReturnZeroVerticalScrollOffset = false;
 
         @Override
-        public void initialize(RecyclerView rv, View scrollView) {
-            super.initialize(rv, scrollView);
+        public void initialize(Context context, RecyclerView rv, View scrollView) {
+            super.initialize(context, rv, scrollView);
 
             scrollView.setTag(this);
         }
@@ -1964,7 +2276,7 @@
                 return null;
             }
 
-            return String.format("Sample item #%d", position);
+            return String.format(Locale.US, "Sample item #%d", position);
         }
 
         @NonNull
@@ -2065,7 +2377,7 @@
         }
 
         String getItemText(int position) {
-            return String.format("Sample item #%d", position);
+            return String.format(Locale.US, "Sample item #%d", position);
         }
 
         @NonNull
@@ -2077,7 +2389,7 @@
 
         @Override
         public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
-            holder.itemView.setMinimumHeight(mItemHeight);
+            holder.itemView.getLayoutParams().height = mItemHeight;
             holder.bind(mData.get(position));
         }
 
@@ -2108,14 +2420,14 @@
         private boolean mIdle = true;
         private ResourceCallback mResourceCallback;
 
-        ScrollIdlingResource(CarUiRecyclerView carUiRecyclerView) {
-            carUiRecyclerView
+        ScrollIdlingResource(CarUiRecyclerView recyclerView) {
+            recyclerView
                     .addOnScrollListener(
-                            new RecyclerView.OnScrollListener() {
+                            new CarUiRecyclerView.OnScrollListener() {
                                 @Override
-                                public void onScrollStateChanged(@NonNull RecyclerView recyclerView,
+                                public void onScrollStateChanged(
+                                        @NonNull CarUiRecyclerView recyclerView,
                                         int newState) {
-                                    super.onScrollStateChanged(recyclerView, newState);
                                     mIdle = (newState == SCROLL_STATE_IDLE
                                             // Treat dragging as idle, or Espresso will
                                             // block itself when swiping.
@@ -2126,8 +2438,9 @@
                                 }
 
                                 @Override
-                                public void onScrolled(@NonNull RecyclerView recyclerView, int dx,
-                                        int dy) {
+                                public void onScrolled(@NonNull CarUiRecyclerView recyclerView,
+                                                       int dx,
+                                                       int dy) {
                                 }
                             });
         }
@@ -2157,4 +2470,26 @@
             return null;
         }
     }
+
+    private boolean isScrollbarEnabledNotAsExpected(boolean expectedValue) {
+        if (mIsPluginEnabled) {
+            int id = mPluginContext.getResources()
+                    .getIdentifier("scrollbar_enable", "bool", mPluginContext.getPackageName());
+            return (mPluginContext.getResources().getBoolean(id) != expectedValue);
+        } else {
+            doReturn(expectedValue).when(mTestableResources)
+                    .getBoolean(R.bool.car_ui_scrollbar_enable);
+            return false;
+        }
+    }
+
+    private int getId(String resourceName) {
+        if (mIsPluginEnabled) {
+            return mPluginContext.getResources().getIdentifier(
+                    resourceName.replace("car_ui_", ""), "id", mPluginContext.getPackageName());
+        } else {
+            return mActivity.getResources()
+                    .getIdentifier(resourceName, "id", mActivity.getPackageName());
+        }
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java
index ae569d2..b87df65 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterTest.java
@@ -28,6 +28,7 @@
 import android.widget.LinearLayout;
 
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
@@ -45,15 +46,11 @@
     ActivityScenario<CarUiRecyclerViewTestActivity> mScenario;
 
     private ContentLimitingAdapter<TestViewHolder> mContentLimitingAdapter;
-    private CarUiRecyclerViewTestActivity mActivity;
 
     @Before
     public void setUp() {
+        mContentLimitingAdapter = new TestContentLimitingAdapter(50);
         mScenario = mActivityRule.getScenario();
-        mScenario.onActivity(activity -> {
-            mActivity = activity;
-            mContentLimitingAdapter = new TestContentLimitingAdapter(50);
-        });
     }
 
     @Test
@@ -151,17 +148,18 @@
         assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        mActivity.runOnUiThread(() -> {
+        String[] msg = new String[] {null};
+        mScenario.onActivity(activity -> {
+            CarUiRecyclerView carUiRecyclerView = activity.requireViewById(R.id.list);
             carUiRecyclerView.setAdapter(mContentLimitingAdapter);
             carUiRecyclerView.setVisibility(View.VISIBLE);
             mContentLimitingAdapter.setMaxItems(0);
             mContentLimitingAdapter.setScrollingLimitedMessageResId(
                     R.string.scrolling_limited_message);
+            msg[0] = activity.getString(R.string.scrolling_limited_message);
         });
 
-        String msg = mActivity.getString(R.string.scrolling_limited_message);
-        onView(withText(msg)).check(matches(isDisplayed()));
+        onView(withText(msg[0])).check(matches(isDisplayed()));
     }
 
     @Test
@@ -169,8 +167,9 @@
         assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        mActivity.runOnUiThread(() -> {
+        String[] msg = new String[] {null};
+        mScenario.onActivity(activity -> {
+            CarUiRecyclerView carUiRecyclerView = activity.requireViewById(R.id.list);
             carUiRecyclerView.setAdapter(mContentLimitingAdapter);
             carUiRecyclerView.setVisibility(View.VISIBLE);
             mContentLimitingAdapter.setMaxItems(0);
@@ -178,10 +177,10 @@
                     R.string.car_ui_scrolling_limited_message);
             mContentLimitingAdapter.setScrollingLimitedMessageResId(
                     R.string.scrolling_limited_message);
+            msg[0] = activity.getString(R.string.scrolling_limited_message);
         });
 
-        String msg = mActivity.getString(R.string.scrolling_limited_message);
-        onView(withText(msg)).check(matches(isDisplayed()));
+        onView(withText(msg[0])).check(matches(isDisplayed()));
     }
 
     @Test
@@ -189,25 +188,50 @@
         assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        CarUiRecyclerView carUiRecyclerView = mActivity.requireViewById(R.id.list);
-        mActivity.runOnUiThread(() -> {
+        String[] msg = new String[] {null};
+        mScenario.onActivity(activity -> {
+            CarUiRecyclerView carUiRecyclerView = activity.requireViewById(R.id.list);
             carUiRecyclerView.setAdapter(mContentLimitingAdapter);
             carUiRecyclerView.setVisibility(View.VISIBLE);
             mContentLimitingAdapter.setMaxItems(0);
+            msg[0] = activity.getString(R.string.car_ui_scrolling_limited_message);
         });
 
-        String msg = mActivity.getString(R.string.car_ui_scrolling_limited_message);
-        onView(withText(msg)).check(matches(isDisplayed()));
+        onView(withText(msg[0])).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_setScrollingLimitedMessageResId_affectsFutureLimiters() {
+        mContentLimitingAdapter.notifyLimitingAnchorChanged(0);
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(50);
+        RecyclerView.ViewHolder last = getItemAtPosition(49);
+        isTestViewHolderWithText(last, "Item 49");
+        mContentLimitingAdapter.setScrollingLimitedMessageResId(R.string.scrolling_limited_message);
+
+        mContentLimitingAdapter.setMaxItems(0);
+
+        assertThat(mContentLimitingAdapter.getItemCount()).isEqualTo(1);
+        last = getItemAtPosition(0);
+        assertThat(last).isInstanceOf(ScrollingLimitedViewHolder.class);
+
+        mScenario.onActivity(activity -> {
+            CarUiRecyclerView carUiRecyclerView = activity.requireViewById(R.id.list);
+            carUiRecyclerView.setAdapter(mContentLimitingAdapter);
+            carUiRecyclerView.setVisibility(View.VISIBLE);
+        });
+
+        onView(withText(R.string.scrolling_limited_message)).check(matches(isDisplayed()));
     }
 
     private RecyclerView.ViewHolder getItemAtPosition(int position) {
         int viewType = mContentLimitingAdapter.getItemViewType(position);
-        RecyclerView.ViewHolder viewHolder =
+        RecyclerView.ViewHolder[] viewHolder = new ViewHolder[] {null};
+        mScenario.onActivity(activity -> viewHolder[0] =
                 mContentLimitingAdapter.createViewHolder(
-                        new LinearLayout(mActivity.getApplicationContext()),
-                        viewType);
-        mContentLimitingAdapter.bindViewHolder(viewHolder, position);
-        return viewHolder;
+                    new LinearLayout(activity.getApplicationContext()),
+                    viewType));
+        mContentLimitingAdapter.bindViewHolder(viewHolder[0], position);
+        return viewHolder[0];
     }
 
     private void isTestViewHolderWithText(RecyclerView.ViewHolder secondToLast, String s) {
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java
index eae42b8..e1e3ace 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/ContentLimitingAdapterUiTest.java
@@ -19,12 +19,12 @@
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
-import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import androidx.recyclerview.widget.RecyclerView;
+import static com.android.car.ui.actions.CarUiRecyclerViewActions.scrollToPosition;
+
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.car.ui.test.R;
@@ -40,15 +40,14 @@
             new ActivityTestRule<>(CarUiRecyclerViewTestActivity.class);
 
     private ContentLimitingAdapter<TestViewHolder> mContentLimitingAdapter;
-    private RecyclerView mCarUiRecyclerView;
+    private CarUiRecyclerView mCarUiRecyclerView;
 
     @Before
     public void setUp() {
         mContentLimitingAdapter = new TestContentLimitingAdapter(50);
         mCarUiRecyclerView = mActivityRule.getActivity().requireViewById(R.id.list);
-        mActivityRule.getActivity().runOnUiThread(() -> {
-            mCarUiRecyclerView.setAdapter(mContentLimitingAdapter);
-        });
+        mActivityRule.getActivity().runOnUiThread(
+                () -> mCarUiRecyclerView.setAdapter(mContentLimitingAdapter));
     }
 
     @Test
@@ -56,9 +55,7 @@
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
         // Switch to limited
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(20);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(20));
         Thread.sleep(300);
         onView(withText("Item 0")).check(matches(isDisplayed()));
         onView(withId(R.id.list)).perform(scrollToPosition(20));
@@ -67,9 +64,7 @@
                 .check(matches(isDisplayed()));
 
         // Switch back to unlimited
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(-1);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(-1));
         Thread.sleep(300);
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
     }
@@ -78,9 +73,7 @@
     public void setMaxItem_toOne() throws Throwable {
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(1);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(1));
         Thread.sleep(300);
         onView(withText("Item 0")).check(matches(isDisplayed()));
         onView(withText("Item 1")).check(doesNotExist());
@@ -88,9 +81,7 @@
                 .check(matches(isDisplayed()));
 
         // Switch back to unlimited
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(-1);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(-1));
         Thread.sleep(300);
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
     }
@@ -99,37 +90,29 @@
     public void setMaxItem_toZero() throws Throwable {
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(0);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(0));
         Thread.sleep(300);
         onView(withText("Item 0")).check(doesNotExist());
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
                 .check(matches(isDisplayed()));
 
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(-1);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(-1));
         Thread.sleep(300);
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
     }
 
     @Test
     public void setMaxItem_toHigherThanTotalItems() throws Throwable {
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(70);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(70));
         Thread.sleep(300);
         onView(withText("Item 0")).check(matches(isDisplayed()));
-        onView(withId(R.id.list)).perform(scrollToPosition(49));
+        mActivityRule.runOnUiThread(() -> mCarUiRecyclerView.scrollToPosition(49));
         onView(withText("Item 49")).check(matches(isDisplayed()));
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message))
                 .check(doesNotExist());
 
         // Switch back to unlimited
-        mActivityRule.runOnUiThread(() -> {
-            mContentLimitingAdapter.setMaxItems(-1);
-        });
+        mActivityRule.runOnUiThread(() -> mContentLimitingAdapter.setMaxItems(-1));
         Thread.sleep(300);
         onView(withId(com.android.car.ui.R.id.car_ui_list_limiting_message)).check(doesNotExist());
     }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapterTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapterTest.java
index b3ad0c1..a941369 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapterTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapterTest.java
@@ -28,23 +28,40 @@
 
 import android.view.View;
 
+import androidx.annotation.IdRes;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.test.R;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.Objects;
 
+@RunWith(Parameterized.class)
 public class DelegatingContentLimitingAdapterTest {
 
+    private boolean mIsPluginEnabled;
+
+    @Parameterized.Parameters
+    public static Object[] data() {
+        // It's important to do no plugin first, so that the plugin will
+        // still be enabled when this test finishes
+        return new Object[]{false, true};
+    }
+
+    @IdRes
+    private static final int DEFAULT_CONFIG_ID = R.id.test_config_id;
+
     @Rule
     public ActivityScenarioRule<CarUiRecyclerViewTestActivity> mActivityRule =
             new ActivityScenarioRule<>(CarUiRecyclerViewTestActivity.class);
@@ -54,6 +71,11 @@
     private TestDelegatingContentLimitingAdapter mDelegateAdapter;
     private CarUiRecyclerViewTestActivity mActivity;
 
+    public DelegatingContentLimitingAdapterTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
+        mIsPluginEnabled = pluginEnabled;
+    }
+
     @Before
     public void setUp() {
         mScenario = mActivityRule.getScenario();
@@ -62,8 +84,11 @@
 
     @Test
     public void setMaxItem_noScrolling_noContentLimiting() {
+        if (mIsPluginEnabled) return;
+
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
@@ -89,8 +114,11 @@
 
     @Test
     public void setMaxItem_noScrolling() {
+        if (mIsPluginEnabled) return;
+
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter.WithContentLimiting(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
@@ -116,8 +144,11 @@
 
     @Test
     public void setMaxItem_withScrolling() {
+        if (mIsPluginEnabled) return;
+
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter.WithContentLimiting(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         onView(withId(R.id.list)).check(matches(isDisplayed()));
 
@@ -136,7 +167,8 @@
     @Test
     public void testChangeItem_callsObservers() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         AdapterDataObserver observer = mock(AdapterDataObserver.class);
         mContentLimitingAdapter.registerAdapterDataObserver(observer);
@@ -159,7 +191,8 @@
     @Test
     public void testInsertItem_callsObservers() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         AdapterDataObserver observer = mock(AdapterDataObserver.class);
         mContentLimitingAdapter.registerAdapterDataObserver(observer);
@@ -182,7 +215,8 @@
     @Test
     public void testRemoveItem_callsObservers() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         AdapterDataObserver observer = mock(AdapterDataObserver.class);
         mContentLimitingAdapter.registerAdapterDataObserver(observer);
@@ -205,7 +239,8 @@
     @Test
     public void testMoveItem_callsObservers() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         AdapterDataObserver observer = mock(AdapterDataObserver.class);
         mContentLimitingAdapter.registerAdapterDataObserver(observer);
@@ -228,7 +263,8 @@
     @Test
     public void testChangeDataSet_callsObservers() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         AdapterDataObserver observer = mock(AdapterDataObserver.class);
         mContentLimitingAdapter.registerAdapterDataObserver(observer);
@@ -262,7 +298,8 @@
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
         mDelegateAdapter.setHasStableIds(false);
 
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         mContentLimitingAdapter.setHasStableIds(true);
 
@@ -272,7 +309,8 @@
     @Test
     public void testGetIds_callsDelegate() {
         mDelegateAdapter = new TestDelegatingContentLimitingAdapter(50);
-        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter, 1);
+        mContentLimitingAdapter = new DelegatingContentLimitingAdapter<>(mDelegateAdapter,
+            DEFAULT_CONFIG_ID);
 
         for (int i = 0; i < 50; i++) {
             assertEquals(mDelegateAdapter.getItemId(i), mContentLimitingAdapter.getItemId(i));
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestDelegatingContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestDelegatingContentLimitingAdapter.java
index 4a59c04..c991819 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestDelegatingContentLimitingAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestDelegatingContentLimitingAdapter.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.recyclerview;
 
+import android.annotation.SuppressLint;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -23,6 +24,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 class TestDelegatingContentLimitingAdapter
@@ -70,15 +72,13 @@
     }
 
     public void insertItemRange(int position, String... items) {
-        for (int i = 0; i < items.length; i++) {
-            mItems.add(position, items[i]);
-        }
+        mItems.addAll(position, Arrays.asList(items));
         notifyItemRangeInserted(position, items.length);
     }
 
     public void removeItemRange(int positionStart, int itemCount) {
         for (int i = 0; i < itemCount; i++) {
-            mItems.remove(positionStart + i);
+            mItems.remove(positionStart);
         }
         notifyItemRangeRemoved(positionStart, itemCount);
     }
@@ -95,6 +95,7 @@
         notifyItemMoved(fromPosition, toPosition);
     }
 
+    @SuppressLint("NotifyDataSetChanged")
     public void changeList(List<String> newItems) {
         mItems.clear();
         mItems.addAll(newItems);
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java
index d6b6899..c341b00 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/recyclerview/TestViewHolder.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.view.View;
 import android.widget.TextView;
 
@@ -24,6 +27,7 @@
 
 import com.android.car.ui.test.R;
 
+@TargetApi(MIN_TARGET_API)
 public class TestViewHolder extends RecyclerView.ViewHolder {
 
     private CharSequence mText;
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifierTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifierTest.java
deleted file mode 100644
index dfc4e53..0000000
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifierTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 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.ui.sharedlibrarysupport;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.pm.PackageInfo;
-
-import androidx.test.core.content.pm.PackageInfoBuilder;
-
-import org.junit.Test;
-
-public class SharedLibrarySpecifierTest {
-
-    @Test
-    public void test_empty_sharedlibraryspecifier_matches_anything() {
-        SharedLibrarySpecifier sharedLibrarySpecifier = SharedLibrarySpecifier.builder()
-                .build();
-
-        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
-                .setPackageName("com.android.car.testsharedlib").build();
-        packageInfo.setLongVersionCode(100);
-
-        assertTrue(sharedLibrarySpecifier.matches(packageInfo));
-    }
-
-    @Test
-    public void test_sharedlibraryspecifier_doesnt_match_different_package_name() {
-        SharedLibrarySpecifier sharedLibrarySpecifier = SharedLibrarySpecifier.builder()
-                .setPackageName("com.android.car.testsharedlib")
-                .build();
-
-        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
-                .setPackageName("com.android.car.testsharedlib2").build();
-
-        assertFalse(sharedLibrarySpecifier.matches(packageInfo));
-    }
-
-    @Test
-    public void test_sharedlibraryspecifier_matches_same_package_name() {
-        SharedLibrarySpecifier sharedLibrarySpecifier = SharedLibrarySpecifier.builder()
-                .setPackageName("com.android.car.testsharedlib")
-                .build();
-
-        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
-                .setPackageName("com.android.car.testsharedlib").build();
-
-        assertTrue(sharedLibrarySpecifier.matches(packageInfo));
-    }
-
-    @Test
-    public void test_sharedlibraryspecifier_doesnt_match_versioncode() {
-        SharedLibrarySpecifier sharedLibrarySpecifier = SharedLibrarySpecifier.builder()
-                .setPackageName("com.android.car.testsharedlib")
-                .setMaxVersion(5)
-                .build();
-
-        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
-                .setPackageName("com.android.car.testsharedlib").build();
-        packageInfo.setLongVersionCode(6);
-
-        assertFalse(sharedLibrarySpecifier.matches(packageInfo));
-    }
-
-    @Test
-    public void test_sharedlibraryspecifier_matches_versioncode() {
-        SharedLibrarySpecifier sharedLibrarySpecifier = SharedLibrarySpecifier.builder()
-                .setPackageName("com.android.car.testsharedlib")
-                .setMaxVersion(5)
-                .build();
-
-        PackageInfo packageInfo = PackageInfoBuilder.newBuilder()
-                .setPackageName("com.android.car.testsharedlib").build();
-        packageInfo.setLongVersionCode(4);
-
-        assertTrue(sharedLibrarySpecifier.matches(packageInfo));
-    }
-}
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/EmptyToolbarController.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/EmptyToolbarController.java
index a984e14..946c163 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/EmptyToolbarController.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/EmptyToolbarController.java
@@ -21,6 +21,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.car.ui.CarUiText;
 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
 
 import java.util.List;
@@ -43,6 +44,11 @@
     }
 
     @Override
+    public void setTitle(CarUiText title) {
+
+    }
+
+    @Override
     public CharSequence getTitle() {
         return null;
     }
@@ -58,6 +64,11 @@
     }
 
     @Override
+    public void setSubtitle(CarUiText text) {
+
+    }
+
+    @Override
     public CharSequence getSubtitle() {
         return null;
     }
@@ -164,6 +175,11 @@
     }
 
     @Override
+    public SearchMode getSearchMode() {
+        return null;
+    }
+
+    @Override
     public void setNavButtonMode(Toolbar.NavButtonMode style) {
 
     }
@@ -174,7 +190,7 @@
     }
 
     @Override
-    public Toolbar.NavButtonMode getNavButtonMode() {
+    public NavButtonMode getNavButtonMode() {
         return null;
     }
 
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/InstallBaseLayoutAroundTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/InstallBaseLayoutAroundTest.java
index a09357e..7c91644 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/InstallBaseLayoutAroundTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/InstallBaseLayoutAroundTest.java
@@ -22,17 +22,21 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import android.annotation.TargetApi;
+
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.FocusParkingView;
 import com.android.car.ui.TrulyEmptyActivity;
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,20 +45,18 @@
 
 /** A test of {@link com.android.car.ui.core.CarUi#installBaseLayoutAround} */
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class InstallBaseLayoutAroundTest {
 
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    private final boolean mSharedLibEnabled;
-
-    public InstallBaseLayoutAroundTest(boolean sharedLibEnabled) {
-        mSharedLibEnabled = sharedLibEnabled;
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public InstallBaseLayoutAroundTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarMenuItemsTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarMenuItemsTest.java
index f91ed89..c3ebad0 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarMenuItemsTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarMenuItemsTest.java
@@ -27,15 +27,16 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static com.android.car.ui.actions.ViewActions.waitForView;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.hamcrest.core.IsNot.not;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.TargetApi;
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.view.View;
@@ -47,7 +48,7 @@
 
 import com.android.car.ui.core.CarUi;
 import com.android.car.ui.matchers.ViewMatchers;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.test.R;
 
 import org.hamcrest.Matcher;
@@ -66,19 +67,17 @@
 
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarMenuItemsTest {
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    private final boolean mSharedLibEnabled;
-
-    public ToolbarMenuItemsTest(boolean sharedLibEnabled) {
-        mSharedLibEnabled = sharedLibEnabled;
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarMenuItemsTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
@@ -239,14 +238,25 @@
         onView(withText("Test title!")).perform(click());
         verify(callback).onClick(menuItem[0]);
 
-        // TODO(b/188925810): this currently isn't supported in the referencedesign shared library.
-        if (!mSharedLibEnabled) {
-            // Open overflow menu, change MenuItem's title, then click on the MenuItem
-            onView(withContentDescription("Overflow")).perform(click());
-            runWithToolbar(toolbar -> menuItem[0].setTitle("Test title 2!"));
-            onView(withText("Test title 2!")).perform(click());
-            verify(callback, times(2)).onClick(menuItem[0]);
-        }
+        // Open overflow menu, change MenuItem's title, then click on the MenuItem
+        onView(withContentDescription("Overflow")).perform(click());
+        runWithToolbar(toolbar -> menuItem[0].setTitle("Test title 2!"));
+        onView(withText("Test title 2!")).perform(click());
+        verify(callback, times(2)).onClick(menuItem[0]);
+    }
+
+    @Test
+    public void menuItems_noOverflow_buttonDoesntExist() {
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            MenuItem menuItem = MenuItem.builder(activity)
+                    .setTitle("Test title!")
+                    .build();
+            toolbar.setMenuItems(Collections.singletonList(menuItem));
+        });
+
+        // Wait for regular MenuItem to show up, then check that the overflow button doesn't exist.
+        onView(isRoot()).perform(waitForView(withText("Test title!")));
+        onView(withContentDescription("Overflow")).check(doesNotExistOrIsNotDisplayed());
     }
 
     @Test
@@ -272,6 +282,37 @@
     }
 
     @Test
+    public void menuItems_overflow2To1_shouldWork() {
+        MenuItem[] menuItem = new MenuItem[] { null, null };
+        runWithActivityAndToolbar((activity, toolbar) -> {
+            menuItem[0] = MenuItem.builder(activity)
+                    .setTitle("Overflow MenuItem 1!")
+                    .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER)
+                    .setOnClickListener(i -> {})
+                    .build();
+            menuItem[1] = MenuItem.builder(activity)
+                    .setTitle("Overflow MenuItem 2!")
+                    .setDisplayBehavior(MenuItem.DisplayBehavior.NEVER)
+                    .setOnClickListener(i -> {})
+                    .build();
+            toolbar.setMenuItems(Arrays.asList(menuItem));
+        });
+
+        onView(isRoot()).perform(waitForView(withContentDescription("Overflow")));
+        onView(withContentDescription("Overflow")).perform(click());
+        onView(withText("Overflow MenuItem 1!")).check(matches(isDisplayed()));
+        onView(withText("Overflow MenuItem 2!")).check(matches(isDisplayed()));
+        onView(withText("Overflow MenuItem 1!")).perform(click());
+
+        runWithToolbar(toolbar -> toolbar.setMenuItems(Collections.singletonList(menuItem[0])));
+
+        onView(isRoot()).perform(waitForView(withContentDescription("Overflow")));
+        onView(withContentDescription("Overflow")).perform(click());
+        onView(withText("Overflow MenuItem 1!")).check(matches(isDisplayed()));
+        onView(withText("Overflow MenuItem 2!")).check(doesNotExistOrIsNotDisplayed());
+    }
+
+    @Test
     public void menuItems_getMenuItems_returnsSameMenuItems() {
         List<MenuItem> menuItems = new ArrayList<>();
         runWithActivityAndToolbar((activity, toolbar) -> {
@@ -332,7 +373,7 @@
 
         runWithToolbar((toolbar) -> menuItem[0].setVisible(false));
 
-        onView(withText("Button!")).check(matches(not(isDisplayed())));
+        onView(withText("Button!")).check(doesNotExistOrIsNotDisplayed());
     }
 
     @Test
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarProgressBarTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarProgressBarTest.java
index 8583b1f..42faa08 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarProgressBarTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarProgressBarTest.java
@@ -20,18 +20,20 @@
 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.hasIndeterminateProgress;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.hamcrest.Matchers.allOf;
 
+import android.annotation.TargetApi;
 import android.widget.ProgressBar;
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,20 +44,21 @@
 
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarProgressBarTest {
 
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    private final boolean mSharedLibEnabled;
+    private final boolean mPluginEnabled;
 
-    public ToolbarProgressBarTest(boolean sharedLibEnabled) {
-        mSharedLibEnabled = sharedLibEnabled;
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarProgressBarTest(boolean pluginEnabled) {
+        mPluginEnabled = pluginEnabled;
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
@@ -66,8 +69,8 @@
     public void test_showProgressBar_works() {
         runWithProgressBar((progressBar) -> progressBar.setVisible(true));
 
-        // We don't want to enforce that the shared lib use a ProgressBar class
-        if (!mSharedLibEnabled) {
+        // We don't want to enforce that the plugin use a ProgressBar class
+        if (!mPluginEnabled) {
             onView(isAssignableFrom(ProgressBar.class)).check(matches(
                     allOf(isDisplayed(), hasIndeterminateProgress())));
         }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarSearchTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarSearchTest.java
index 132cba8..ca6d564 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarSearchTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarSearchTest.java
@@ -20,15 +20,23 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
 import static androidx.test.espresso.matcher.ViewMatchers.withHint;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.actions.ViewActions.waitForNoMatchingView;
+import static com.android.car.ui.actions.ViewActions.waitForView;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 import static com.android.car.ui.matchers.ViewMatchers.withDrawable;
 
+import static junit.framework.TestCase.fail;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.TargetApi;
+import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.widget.EditText;
 
@@ -36,37 +44,44 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.test.R;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
 
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.IOException;
 import java.util.function.Consumer;
 
-/** Unit test for the search functionality in {@link ToolbarController}. */
+/**
+ * Unit test for the search functionality in {@link ToolbarController}.
+ */
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarSearchTest {
     @Parameterized.Parameters
     public static Object[][] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
-        return new Object[][] {
-            new Object[] {false, SearchMode.SEARCH},
-            new Object[] {false, SearchMode.EDIT},
-            new Object[] {true, SearchMode.SEARCH},
-            new Object[] {true, SearchMode.EDIT},
+        return new Object[][]{
+                new Object[]{false, SearchMode.SEARCH},
+                new Object[]{false, SearchMode.EDIT},
+                new Object[]{true, SearchMode.SEARCH},
+                new Object[]{true, SearchMode.EDIT},
         };
     }
 
     private final SearchMode mSearchMode;
+    private final boolean mIsPluginEnabled;
 
-    public ToolbarSearchTest(boolean sharedLibEnabled, SearchMode searchMode) {
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarSearchTest(boolean pluginEnabled, SearchMode searchMode) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
         mSearchMode = searchMode;
+        mIsPluginEnabled = pluginEnabled;
     }
 
     @Rule
@@ -120,10 +135,10 @@
         onView(withDrawable(context, R.drawable.ic_launcher)).check(matches(isDisplayed()));
         if (mSearchMode == SearchMode.SEARCH) {
             onView(withDrawable(context, R.drawable.ic_settings_gear))
-                .check(matches(isDisplayed()));
+                    .check(matches(isDisplayed()));
         } else {
             onView(withDrawable(context, R.drawable.ic_settings_gear))
-                .check(doesNotExistOrIsNotDisplayed());
+                    .check(doesNotExistOrIsNotDisplayed());
         }
     }
 
@@ -137,7 +152,7 @@
 
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         onView(withDrawable(context, R.drawable.ic_settings_gear))
-            .check(doesNotExistOrIsNotDisplayed());
+                .check(doesNotExistOrIsNotDisplayed());
     }
 
     @Test
@@ -166,10 +181,127 @@
         onView(withHint("Test title!")).check(matches(isDisplayed()));
     }
 
+    @Test
+    public void test_setSearchHint_uxRestricted() {
+        // Rely on test_setSearchHint_uxRestricted_injectedEvents for plugin testing
+        if (mIsPluginEnabled) {
+            return;
+        }
+
+        runWithToolbar((toolbar) -> {
+            toolbar.setSearchHint("Test search hint");
+            toolbar.setSearchMode(mSearchMode);
+        });
+
+        onView(withHint("Test search hint")).check(matches(isDisplayed()));
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        mScenarioRule.getScenario().onActivity(activity -> {
+            CarUxRestrictions keyboardRestriction = new CarUxRestrictions.Builder(true,
+                    CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD, 0).build();
+            CarUxRestrictionsUtil.getInstance(activity).setUxRestrictions(keyboardRestriction);
+        });
+
+        onView(withHint(context.getString(R.string.car_ui_restricted_while_driving))).check(
+                matches(isDisplayed()));
+
+        mScenarioRule.getScenario().onActivity(activity -> {
+            CarUxRestrictions fullRestriction = new CarUxRestrictions.Builder(true,
+                    CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED, 0).build();
+            CarUxRestrictionsUtil.getInstance(activity).setUxRestrictions(fullRestriction);
+        });
+
+        onView(withHint(context.getString(R.string.car_ui_restricted_while_driving))).check(
+                matches(isDisplayed()));
+
+        mScenarioRule.getScenario().onActivity(activity -> {
+            CarUxRestrictions baselineRestriction = new CarUxRestrictions.Builder(true,
+                    CarUxRestrictions.UX_RESTRICTIONS_BASELINE, 0).build();
+            CarUxRestrictionsUtil.getInstance(activity).setUxRestrictions(baselineRestriction);
+        });
+
+        onView(withHint("Test search hint")).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void test_setSearchHint_uxRestricted_injectedEvents() {
+        try {
+            runWithToolbar((toolbar) -> {
+                toolbar.setSearchHint("Test search hint");
+                toolbar.setSearchMode(mSearchMode);
+            });
+
+            onView(withHint("Test search hint")).check(matches(isDisplayed()));
+
+            injectDrivingState();
+            onView(isRoot()).perform(waitForNoMatchingView(withHint("Test search hint"), 1500));
+
+            runWithToolbar((toolbar) -> toolbar.setSearchHint("New hint"));
+            onView(isRoot()).perform(waitForNoMatchingView(withHint("New hint"), 1500));
+
+            injectParkedState();
+            onView(isRoot()).perform(waitForView(withHint("New hint"), 1500));
+        } finally {
+            // Always return to parked state after tests to ensure future tests are not
+            // influenced by driving state.
+            injectParkedState();
+        }
+    }
+
+    @Test
+    public void test_setSearchHint_uxRestricted_injectedEvents_startRestricted() {
+        injectDrivingState();
+
+        try {
+            runWithToolbar((toolbar) -> {
+                toolbar.setSearchHint("Test search hint");
+                toolbar.setSearchMode(mSearchMode);
+            });
+
+            onView(isRoot()).perform(waitForNoMatchingView(withHint("Test search hint"), 1500));
+
+            injectParkedState();
+            onView(isRoot()).perform(waitForView(withHint("Test search hint"), 1500));
+        } finally {
+            // Always return to parked state after tests to ensure future tests are not
+            // influenced by driving state.
+            injectParkedState();
+        }
+    }
+
     private void runWithToolbar(Consumer<ToolbarController> toRun) {
         mScenarioRule.getScenario().onActivity(activity -> {
             ToolbarController toolbar = CarUi.requireToolbar(activity);
             toRun.accept(toolbar);
         });
     }
+
+    private void injectDrivingState() {
+        Runtime runtime = Runtime.getRuntime();
+        try {
+            // Set gear to Drive
+            runtime.exec("cmd car_service inject-vhal-event 0x11400400 8");
+            // Set speed to 30 meters per second
+            runtime.exec("cmd car_service inject-vhal-event 0x11600207 30 -t 2000");
+            // Remove parking break
+            runtime.exec("cmd car_service inject-vhal-event 0x11200402 false");
+        } catch (IOException e) {
+            fail(e.getLocalizedMessage());
+        }
+    }
+
+    private void injectParkedState() {
+        Runtime runtime = Runtime.getRuntime();
+        try {
+            // Set speed to 0
+            runtime.exec("cmd car_service inject-vhal-event 0x11600207 0");
+            // Set gear to Parked
+            runtime.exec("cmd car_service inject-vhal-event 0x11400400 4");
+            // Set parking break
+            runtime.exec("cmd car_service inject-vhal-event 0x11200402 true");
+        } catch (IOException e) {
+            fail(e.getLocalizedMessage());
+        }
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabDeprecatedTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabDeprecatedTest.java
index ba19a6e..809fc2e 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabDeprecatedTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabDeprecatedTest.java
@@ -19,16 +19,20 @@
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.TargetApi;
+
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,17 +45,18 @@
 
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarTabDeprecatedTest {
 
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    public ToolbarTabDeprecatedTest(boolean sharedLibEnabled) {
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarTabDeprecatedTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabTest.java
index 9cb4e71..3dd0170 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTabTest.java
@@ -19,14 +19,18 @@
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 
 import androidx.core.content.ContextCompat;
@@ -34,7 +38,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.test.R;
 
 import org.junit.Rule;
@@ -48,17 +52,18 @@
 
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarTabTest {
 
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    public ToolbarTabTest(boolean sharedLibEnabled) {
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarTabTest(boolean pluginEnabled) {
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
@@ -145,6 +150,23 @@
         onView(withText("Tab 2")).check(doesNotExistOrIsNotDisplayed());
     }
 
+    @Test
+    public void test_selectTab_onEmptyToolbars() {
+        runWithToolbar(toolbar -> {
+            toolbar.setTabs(null);
+            assertThrows(IllegalArgumentException.class, () -> toolbar.selectTab(0));
+        });
+    }
+
+    @Test
+    public void test_getSelectedTab_onEmptyToolbars() {
+        runWithToolbar(toolbar -> {
+            toolbar.setTabs(null);
+            assertThrows(IllegalArgumentException.class, () -> toolbar.selectTab(0));
+            assertEquals(-1, toolbar.getSelectedTab());
+        });
+    }
+
     private void runWithToolbar(Consumer<ToolbarController> toRun) {
         mScenarioRule.getScenario().onActivity(activity -> {
             ToolbarController toolbar = CarUi.requireToolbar(activity);
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
index 0971722..7620893 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/toolbar/ToolbarTest.java
@@ -23,6 +23,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.matchers.ViewMatchers.doesNotExistOrIsNotDisplayed;
 import static com.android.car.ui.matchers.ViewMatchers.withDrawable;
 
@@ -31,6 +32,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.assertEquals;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 
 import androidx.core.content.ContextCompat;
@@ -39,7 +41,7 @@
 
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.core.CarUi;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.test.R;
 
 import org.junit.Rule;
@@ -53,20 +55,21 @@
 /** Unit test for {@link ToolbarController}. */
 @SuppressWarnings("AndroidJdkLibsChecker")
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class ToolbarTest {
 
     @Parameterized.Parameters
     public static Object[] data() {
-        // It's important to do no shared library first, so that the shared library will
+        // It's important to do no plugin first, so that the plugin will
         // still be enabled when this test finishes
         return new Object[] { false, true };
     }
 
-    private final boolean mSharedLibEnabled;
+    private final boolean mPluginEnabled;
 
-    public ToolbarTest(boolean sharedLibEnabled) {
-        mSharedLibEnabled = sharedLibEnabled;
-        SharedLibraryFactorySingleton.setSharedLibEnabled(sharedLibEnabled);
+    public ToolbarTest(boolean pluginEnabled) {
+        mPluginEnabled = pluginEnabled;
+        PluginFactorySingleton.setPluginEnabledForTesting(pluginEnabled);
     }
 
     @Rule
@@ -89,13 +92,13 @@
     }
 
     /**
-     * This is somewhat of a bug, but various tests in other apps rely on this functionality.
+     * Various tests in other apps rely on this functionality.
      */
     @Test
     public void test_setTitle_null_returns_nonNull() {
         CharSequence[] getTitleResult = new CharSequence[] {"Something obviously incorrect"};
         runWithToolbar((toolbar) -> {
-            toolbar.setTitle(null);
+            toolbar.setTitle((CharSequence) null);
             getTitleResult[0] = toolbar.getTitle();
         });
 
@@ -103,13 +106,13 @@
     }
 
     /**
-     * This is somewhat of a bug, but various tests in other apps rely on this functionality.
+     * Various tests in other apps rely on this functionality.
      */
     @Test
     public void test_setSubtitle_null_returns_nonNull() {
         CharSequence[] getTitleResult = new CharSequence[] {"Something obviously incorrect"};
         runWithToolbar((toolbar) -> {
-            toolbar.setSubtitle(null);
+            toolbar.setSubtitle((CharSequence) null);
             getTitleResult[0] = toolbar.getSubtitle();
         });
 
@@ -135,14 +138,14 @@
             toolbar.setSearchHint("Foo2");
             toolbar.setShowMenuItemsWhileSearching(true);
             toolbar.setState(Toolbar.State.SUBPAGE);
-            toolbar.setNavButtonMode(Toolbar.NavButtonMode.CLOSE);
+            toolbar.setNavButtonMode(NavButtonMode.CLOSE);
 
             assertThat(toolbar.getTitle().toString()).isEqualTo("Foo");
             assertThat(toolbar.getSearchHint().toString()).isEqualTo("Foo2");
             assertThat(toolbar.getShowMenuItemsWhileSearching()).isEqualTo(true);
             assertThat(toolbar.getState()).isEquivalentAccordingToCompareTo(Toolbar.State.SUBPAGE);
             assertThat(toolbar.getNavButtonMode()).isEquivalentAccordingToCompareTo(
-                    Toolbar.NavButtonMode.CLOSE);
+                    NavButtonMode.CLOSE);
         });
     }
 
@@ -237,9 +240,9 @@
             backgroundShown[0] = toolbar.getBackgroundShown();
         });
 
-        if (mSharedLibEnabled) {
-            // Shared lib doesn't support hiding the background
-            // Temporarily disabled while we're not using the shared lib
+        if (mPluginEnabled) {
+            // The plugin doesn't support hiding the background
+            // Temporarily disabled while we're not using the plugin
             // assertThat(backgroundShown[0]).isTrue();
         } else {
             assertThat(backgroundShown[0]).isFalse();
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/uxr/DrawableStateViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/uxr/DrawableStateViewTest.java
index e3ccd3a..9af0d67 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/uxr/DrawableStateViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/uxr/DrawableStateViewTest.java
@@ -15,8 +15,11 @@
  */
 package com.android.car.ui.uxr;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static org.junit.Assert.assertNotNull;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.AttributeSet;
@@ -43,6 +46,7 @@
 import java.util.stream.Collectors;
 
 @RunWith(Parameterized.class)
+@TargetApi(MIN_TARGET_API)
 public class DrawableStateViewTest {
 
     private static final List<Class<? extends DrawableStateView>> ALL_DRAWABLE_STATE_VIEWS =
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/widget/CarUiTextViewTest.java b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/widget/CarUiTextViewTest.java
index 65c46a4..256084a 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/widget/CarUiTextViewTest.java
+++ b/car-ui-lib/car-ui-lib/src/androidTest/java/com/android/car/ui/widget/CarUiTextViewTest.java
@@ -20,20 +20,31 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.withHint;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import static org.junit.Assert.assertEquals;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.TargetApi;
 import android.graphics.Color;
+import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.Spanned;
+import android.text.TextUtils;
 import android.text.style.ForegroundColorSpan;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.core.content.ContextCompat;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.car.ui.CarUiText;
+import com.android.car.ui.R;
 import com.android.car.ui.TestActivity;
 
 import org.junit.Before;
@@ -46,7 +57,9 @@
 /**
  * Unit tests for {@link CarUiTextViewTest}.
  */
+@TargetApi(MIN_TARGET_API)
 public class CarUiTextViewTest {
+    private static final String ELLIPSIS = "…";
     private static final CharSequence LONG_CHAR_SEQUENCE =
             "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "
                     + "incididunt ut labore et dolore magna aliqua. Netus et malesuada fames ac "
@@ -69,9 +82,7 @@
 
     @Before
     public void setUp() {
-        mActivityRule.getScenario().onActivity(activity -> {
-            mActivity = activity;
-        });
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
     }
 
     @Test
@@ -139,6 +150,259 @@
         container.post(() -> container.addView(textView));
 
         onView(withHint(hint)).check(matches(isDisplayed()));
-        assertEquals(text, new SpannableString(textView.getText()));
+        assertTrue(TextUtils.equals(text, textView.getText()));
+    }
+
+    @Test
+    public void testTextVariants_withCharLimit() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CharSequence> variants = new ArrayList<>();
+        variants.add("Long string");
+        variants.add("Short");
+        CarUiText text = new CarUiText.Builder(variants).setMaxLines(1).setMaxChars(5).build();
+        textView.setText(text);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for no manual truncation ellipsis.
+        onView(withId(id)).check(matches(not(withText(containsString(ELLIPSIS)))));
+
+        onView(withId(id)).check(
+                matches(withText(containsString("Short"))));
+    }
+
+    @Test
+    public void testTextVariants_withCharLimitNoMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CharSequence> variants = new ArrayList<>();
+        variants.add("Long string");
+        variants.add("Short");
+        CarUiText text = new CarUiText.Builder(variants).setMaxLines(Integer.MAX_VALUE).setMaxChars(
+                5).build();
+        textView.setText(text);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+        // Check for no manual truncation ellipsis.
+        onView(withId(id)).check(matches(not(withText(containsString(ELLIPSIS)))));
+
+        onView(withId(id)).check(matches(withText(containsString("Short"))));
+    }
+
+    @Test
+    public void testTextVariants_noFit() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CharSequence> variants = new ArrayList<>();
+        String marker = "MARKING AS PREFERRED VARIANT";
+        variants.add(marker + LONG_CHAR_SEQUENCE);
+        variants.add(LONG_CHAR_SEQUENCE);
+        variants.add(LONG_CHAR_SEQUENCE);
+        CarUiText text = new CarUiText.Builder(variants).setMaxLines(2).build();
+        textView.setText(text);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for manual truncation ellipsis.
+        onView(withId(id)).check(matches(withText(containsString(ELLIPSIS))));
+        assertEquals(2, textView.getLineCount());
+        onView(withId(id)).check(matches(withText(containsString(marker))));
+    }
+
+    @Test
+    public void testTextVariants() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CharSequence> variants = new ArrayList<>();
+        variants.add(LONG_CHAR_SEQUENCE);
+        variants.add("Short string");
+        CarUiText text = new CarUiText.Builder(variants).setMaxLines(1).build();
+        textView.setText(text);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for no manual truncation ellipsis.
+        onView(withId(id)).check(matches(not(withText(containsString(ELLIPSIS)))));
+
+        assertEquals(1, textView.getLineCount());
+    }
+
+    @Test
+    public void testTextTruncation_twoShortLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText.Builder("Short text string").setMaxLines(2).build());
+        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for no manual truncation ellipsis.
+        onView(withId(id)).check(matches(not(withText(containsString(ELLIPSIS)))));
+    }
+
+    @Test
+    public void testTextTruncation_oneLongOneShort_withMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(2).build());
+        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for manual truncation ellipsis.
+        onView(withId(id)).check(matches(withText(containsString(ELLIPSIS))));
+
+        assertEquals(3, textView.getLineCount());
+    }
+
+    @Test
+    public void testTextTruncation_oneLongOneShort_noMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(Integer.MAX_VALUE).build());
+        lines.add(new CarUiText.Builder("Second short string").setMaxLines(2).build());
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for no manual truncation ellipsis.
+        onView(withId(id)).check(matches(not(withText(containsString(ELLIPSIS)))));
+    }
+
+    @Test
+    public void testTextTruncation_twoLong_withMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(3).build());
+        lines.add(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(3).build());
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for manual truncation ellipsis.
+        onView(withId(id)).check(matches(withText(containsString(ELLIPSIS))));
+
+        assertEquals(6, textView.getLineCount());
+    }
+
+    @Test
+    public void testTitleTextTruncation_withMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        textView.setText(new CarUiText.Builder(LONG_CHAR_SEQUENCE).setMaxLines(2).build());
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for manual truncation ellipsis.
+        onView(withId(id)).check(matches(withText(containsString(ELLIPSIS))));
+
+        assertEquals(2, textView.getLineCount());
+    }
+
+    @Test
+    public void testTextTruncation_twoLong_differentMaxLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText(LONG_CHAR_SEQUENCE, 1));
+        lines.add(new CarUiText(LONG_CHAR_SEQUENCE, 4));
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        // Check for manual truncation ellipsis.
+        onView(withId(id)).check(matches(withText(containsString(ELLIPSIS))));
+
+        assertEquals(5, textView.getLineCount());
+    }
+
+    @Test
+    public void testMultipleBodyTextLines() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        CharSequence line1 = "First short string";
+        CharSequence line2 = "Second short string";
+        CharSequence line3 = "Third short string";
+
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText.Builder(line1).build());
+        lines.add(new CarUiText.Builder(line2).build());
+        lines.add(new CarUiText.Builder(line3).build());
+
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        String expectedText = line1 + "\n" + line2 + "\n" + line3;
+        onView(withText(containsString(expectedText))).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testBodyTextSpans() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        int color = ContextCompat.getColor(mActivity, R.color.car_ui_color_accent);
+
+        Spannable line1 = new SpannableString("This text contains color");
+        line1.setSpan(new ForegroundColorSpan(color), 19, 24, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        List<CarUiText> lines = new ArrayList<>();
+        lines.add(new CarUiText(line1, Integer.MAX_VALUE));
+
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        onView(withId(id)).check(matches(isDisplayed()));
+        assertTrue(TextUtils.equals(line1, textView.getText()));
+    }
+
+    @Test
+    public void testTextWithLineBreak() {
+        CarUiTextView textView = CarUiTextView.create(mActivity);
+        int id = View.generateViewId();
+        textView.setId(id);
+        List<CarUiText> lines = new ArrayList<>();
+        String firstTwoLines = "This is first line\nThis is the second line";
+        String thirdLine = "\nThis is the third line";
+        lines.add(new CarUiText(firstTwoLines + thirdLine, 2));
+
+        textView.setText(lines);
+        ViewGroup container = mActivity.findViewById(
+                com.android.car.ui.test.R.id.test_container);
+        container.post(() -> container.addView(textView));
+
+        onView(withId(id)).check(matches(isDisplayed()));
+        onView(withId(id)).check(matches(withText(firstTwoLines)));
+        onView(withId(id)).check(matches(not(withText(thirdLine))));
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_dividers_test_activity.xml
similarity index 67%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_dividers_test_activity.xml
index 6a35b43..2cb0fc5 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/car_ui_recycler_view_dividers_test_activity.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright (C) 2021 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,11 +14,14 @@
   ~ 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"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:gravity="center">
+    android:layout_height="match_parent">
+  <com.android.car.ui.recyclerview.CarUiRecyclerView
+      android:id="@+id/list"
+      app:enableDivider="true"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"/>
 </FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
index 6eb1fbd..a8902bb 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/values/strings.xml
@@ -40,6 +40,12 @@
     <string name="title_twoaction_preference">TwoAction preference</string>
     <string name="summary_twoaction_preference">A widget should be visible on the right</string>
     <string name="twoaction_secondary_text">Secondary text</string>
+    <string name="title">Title!</string>
+    <string name="subtitle">Subtitle!</string>
+    <string name="message">Message!</string>
+    <string name="positive">Positive!</string>
+    <string name="negative">Negative!</string>
+    <string name="neutral">Neutral!</string>
 
 
     <string-array name="test_string_array">
diff --git a/car-messenger-common/res/values/dimens.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/values/values.xml
similarity index 71%
rename from car-messenger-common/res/values/dimens.xml
rename to car-ui-lib/car-ui-lib/src/androidTest/res/values/values.xml
index ea87725..232ce3d 100644
--- a/car-messenger-common/res/values/dimens.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/values/values.xml
@@ -1,6 +1,6 @@
-<?xml version='1.0' encoding='UTF-8'?>
+<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright 2020 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,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+
 <resources>
-    <dimen name="notification_contact_photo_size">300dp</dimen>
-    <dimen name="contact_avatar_corner_radius_percent" format="float">0.5</dimen>
+    <id name="test_config_id"/>
 </resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/AndroidManifest.xml b/car-ui-lib/car-ui-lib/src/main/AndroidManifest.xml
index 5c2b9ae..7bedab5 100644
--- a/car-ui-lib/car-ui-lib/src/main/AndroidManifest.xml
+++ b/car-ui-lib/car-ui-lib/src/main/AndroidManifest.xml
@@ -16,11 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.car.ui">
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.car.ui">
+    <!-- This is set to 16, because this is the lowest supported version across all our clients.
+     We're setting this so we don't get lint errors for non-gradle builds. -->
+    <uses-sdk
+        android:minSdkVersion="16" />
     <queries>
-        <intent>
-            <action android:name="com.android.car.ui.intent.action.SHARED_LIBRARY"/>
-        </intent>
+      <provider
+          android:name="com.android.car.ui.plugin.PluginNameProvider"
+          android:authorities="com.android.car.ui.plugin"
+          tools:ignore="ExportedContentProvider,MissingClass" />
     </queries>
     <application>
         <provider
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
index ef2cb49..f40d2b3 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/AlertDialogBuilder.java
@@ -17,10 +17,12 @@
 
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TITLE_TO_CONTENT_AREA;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TO_CONTENT_AREA;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
 
+import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
@@ -28,6 +30,8 @@
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.InputFilter;
@@ -58,7 +62,11 @@
 
 /**
  * Wrapper for AlertDialog.Builder
+ * <p>
+ * Rendered views will comply with
+ * <a href="https://source.android.com/devices/automotive/hmi/car_ui/appendix_b">customization guardrails</a>
  */
+@TargetApi(MIN_TARGET_API)
 public class AlertDialogBuilder {
 
     private AlertDialog.Builder mBuilder;
@@ -86,12 +94,15 @@
 
         @Override
         public void onTextChanged(CharSequence s, int start, int before, int count) {
-            Bundle bundle = new Bundle();
-            String titleString = mWideScreenTitle != null ? mWideScreenTitle : mTitle.toString();
-            bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, titleString);
-            bundle.putString(ADD_DESC_TO_CONTENT_AREA, s.toString());
-            mInputMethodManager.sendAppPrivateCommand(mCarUiEditText, WIDE_SCREEN_ACTION,
+            if (VERSION.SDK_INT >= VERSION_CODES.R) {
+                Bundle bundle = new Bundle();
+                String titleString = mWideScreenTitle != null ? mWideScreenTitle
+                        : mTitle.toString();
+                bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, titleString);
+                bundle.putString(ADD_DESC_TO_CONTENT_AREA, s.toString());
+                mInputMethodManager.sendAppPrivateCommand(mCarUiEditText, WIDE_SCREEN_ACTION,
                     bundle);
+            }
         }
 
         @Override
@@ -122,7 +133,7 @@
         return v.onApplyWindowInsets(insets);
     };
 
-    private final AlertDialog.OnDismissListener mOnDismissListener = dialog -> {
+    private final DialogInterface.OnDismissListener mOnDismissListener = dialog -> {
         if (mRoot != null) {
             mRoot.setOnApplyWindowInsetsListener(null);
         }
@@ -442,9 +453,10 @@
     private void setCustomList(@NonNull CarUiListItemAdapter adapter) {
         View customList = LayoutInflater.from(mContext).inflate(
                 R.layout.car_ui_alert_dialog_list, null);
-        RecyclerView mList = CarUiUtils.requireViewByRefId(customList, R.id.list);
-        mList.setLayoutManager(new LinearLayoutManager(mContext));
-        mList.setAdapter(adapter);
+        RecyclerView list = CarUiUtils.requireViewByRefId(customList, R.id.list);
+        list.setLayoutManager(new LinearLayoutManager(mContext));
+        list.setAdapter(adapter);
+        list.setFocusable(false);
         mBuilder.setView(customList);
     }
 
@@ -689,8 +701,8 @@
      *
      * @param prompt              the string that will be set on the edit text view
      * @param textChangedListener textWatcher whose methods are called whenever this TextView's text
-     *                            changes {@link null} otherwise.
-     * @param inputFilters        list of input filters, {@link null} if no filter is needed
+     *                            changes {@code null} otherwise.
+     * @param inputFilters        list of input filters, {@code null} if no filter is needed
      * @param inputType           See {@link EditText#setInputType(int)}, except
      *                            {@link android.text.InputType#TYPE_NULL} will not be set.
      * @return this Builder object to allow for chaining of calls to set methods
@@ -724,8 +736,8 @@
      *
      * @param prompt              the string that will be set on the edit text view
      * @param textChangedListener textWatcher whose methods are called whenever this TextView's text
-     *                            changes {@link null} otherwise.
-     * @param inputFilters        list of input filters, {@link null} if no filter is needed
+     *                            changes {@code null} otherwise.
+     * @param inputFilters        list of input filters, {@code null} if no filter is needed
      * @return this Builder object to allow for chaining of calls to set methods
      */
     public AlertDialogBuilder setEditBox(String prompt, TextWatcher textChangedListener,
@@ -800,7 +812,11 @@
             mIconView.setImageTintList(
                     mContext.getColorStateList(R.color.car_ui_dialog_icon_color));
         }
-        mBuilder.setCustomTitle(customTitle);
+        // Do not set custom title if not required to maintain extra padding for messages with no
+        // title logic
+        if (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle) || mIcon != null) {
+            mBuilder.setCustomTitle(customTitle);
+        }
 
         if (!mAllowDismissButton && !mHasSingleChoiceBodyButton
                 && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiLayoutInflaterFactory.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiLayoutInflaterFactory.java
index f87aec1..859533a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiLayoutInflaterFactory.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiLayoutInflaterFactory.java
@@ -15,22 +15,30 @@
  */
 package com.android.car.ui;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatViewInflater;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.widget.CarUiTextView;
 
 /**
  * A custom {@link LayoutInflater.Factory2} that will create CarUi components such as {@link
  * CarUiRecyclerView}. It extends AppCompatViewInflater so that it can still let AppCompat
  * components be created correctly.
  */
+@TargetApi(MIN_TARGET_API)
 public class CarUiLayoutInflaterFactory extends AppCompatViewInflater
         implements LayoutInflater.Factory2 {
 
@@ -41,15 +49,35 @@
         // Don't use CarUiTextView.class.getSimpleName(), as when proguard obfuscates the class name
         // it will no longer match what's in xml.
         if (CarUiRecyclerView.class.getName().equals(name)) {
-            view = SharedLibraryFactorySingleton.get(context)
-                    .createRecyclerView(context, attrs);
+            view = PluginFactorySingleton.get(context)
+                    .createRecyclerView(context, attrs).getView();
         } else if (name.contentEquals("CarUiTextView")) {
-            view = SharedLibraryFactorySingleton.get(context).createTextView(context, attrs);
+            view = PluginFactorySingleton.get(context).createTextView(context, attrs);
+        } else if (("androidx.recyclerview.widget." + "RecyclerView").equals(name)) {
+            // Some apps use the old android.support.v7.widget.RecyclerView package name for the
+            // RecyclerView. When RROs are applied, they must also use that old package name to
+            // instantiate the RecyclerView. So if an RRO is found using the new package name,
+            // inflate a RecyclerView using the old package name. We are using the new package name
+            // here, but when car-ui-lib is included in one of those apps that uses the old package
+            // name, the RecyclerView class is renamed.
+            view = new RecyclerView(context, attrs);
+        } else if ("TextView".equals(name)) {
+            // Replace all TextView occurrences with CarUiTextView to support older RROs that still
+            // use TextView where CarUiTextView is now expected. ie. `car_ui_list_item.xml`.
+            view = PluginFactorySingleton.get(context).createTextView(context, attrs);
         }
 
         return view;
     }
 
+    @NonNull
+    @Override
+    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+        // Replace all TextView occurrences with CarUiTextView to support older RROs that still
+        // use TextView where CarUiTextView is now expected. ie. `car_ui_list_item.xml`.
+        return CarUiTextView.create(context, attrs);
+    }
+
     @Override
     public View onCreateView(String name, Context context, AttributeSet attrs) {
         // Deprecated, do nothing.
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiText.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiText.java
index 7e08dfc..15b1651 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiText.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/CarUiText.java
@@ -159,7 +159,7 @@
          *
          * @param text text to display
          */
-        public Builder(CharSequence text) {
+        public Builder(@NonNull CharSequence text) {
             this(Collections.singletonList(text));
         }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusAreaAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusAreaAdapterV1.java
index c225b54..c9e6549 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusAreaAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusAreaAdapterV1.java
@@ -18,10 +18,10 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
-import com.android.car.ui.sharedlibrary.oemapis.FocusAreaOEMV1;
+import com.android.car.ui.plugin.oemapis.FocusAreaOEMV1;
 
 /**
- * Adapter from {@link FocusArea} to {@link FocusAreaOEMV1}.
+ * Adapter from {@link com.android.car.ui.FocusArea} to {@link FocusAreaOEMV1}.
  */
 public class FocusAreaAdapterV1 implements FocusAreaOEMV1 {
     private final FocusArea mFocusArea;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingViewAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingViewAdapterV1.java
index 2152e6e..7dbfa24 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingViewAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/FocusParkingViewAdapterV1.java
@@ -17,10 +17,10 @@
 
 import android.view.View;
 
-import com.android.car.ui.sharedlibrary.oemapis.FocusParkingViewOEMV1;
+import com.android.car.ui.plugin.oemapis.FocusParkingViewOEMV1;
 
 /**
- * Adapter from {@link FocusParkingView} to {@link FocusParkingViewOEMV1}.
+ * Adapter from {@link com.android.car.ui.FocusParkingView} to {@link FocusParkingViewOEMV1}.
  */
 public class FocusParkingViewAdapterV1 implements FocusParkingViewOEMV1 {
     private final FocusParkingView mFocusParkingView;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/SecureView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/SecureView.java
new file mode 100644
index 0000000..73df335
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/SecureView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.ui;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A view that cannot be clicked on when the window is obscured.
+ */
+@TargetApi(MIN_TARGET_API)
+public class SecureView extends View {
+
+    private boolean mSecure = true;
+
+    public SecureView(Context context) {
+        super(context);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+        if (!mSecure) {
+            return super.onFilterTouchEventForSecurity(event);
+        }
+
+        int flags = MotionEvent.FLAG_WINDOW_IS_OBSCURED;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            flags |= MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        }
+
+        return (event.getFlags() & flags) == 0 && super.onFilterTouchEventForSecurity(event);
+    }
+
+    /** Sets if this view should ignore touch events when the window is obscured. Default true. */
+    public void setSecure(boolean secure) {
+        mSecure = secure;
+    }
+
+    /** Returns if this view should ignore touch events when the window is obscured. */
+    public boolean isSecure() {
+        return mSecure;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialog.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialog.java
index 58ebc87..fc35180 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialog.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialog.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.appstyledview;
 
+import android.app.Activity;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -26,8 +27,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledDismissListener;
-
 /**
  * App styled dialog used to display a view that cannot be customized via OEM. Dialog will inflate a
  * layout and add the view provided by the application into the layout. Everything other than the
@@ -35,14 +34,18 @@
  *
  * Apps should not use this directly. App's should use {@link AppStyledDialogController}.
  */
-public class AppStyledDialog extends Dialog implements DialogInterface.OnDismissListener {
+/* package */ class AppStyledDialog extends Dialog implements DialogInterface.OnDismissListener {
 
     private final AppStyledViewController mController;
-    private AppStyledDismissListener mOnDismissListener;
+    private Runnable mOnDismissListener;
     private View mContent;
+    private final Context mContext;
 
-    public AppStyledDialog(@NonNull Context context, AppStyledViewController controller) {
+    public AppStyledDialog(@NonNull Context context, @NonNull AppStyledViewController controller) {
         super(context);
+        // super.getContext() returns a ContextThemeWrapper which is not an Activity which we need 
+        // in order to get call getWindow()
+        mContext = context;
         mController = controller;
         setOnDismissListener(this);
     }
@@ -60,15 +63,46 @@
     @Override
     public void onDismiss(DialogInterface dialog) {
         if (mOnDismissListener != null) {
-            mOnDismissListener.onDismiss();
+            mOnDismissListener.run();
         }
     }
 
+    /**
+     * An hack used to show the dialogs in Immersive Mode (that is with the NavBar hidden). To
+     * obtain this, the method makes the dialog not focusable before showing it, change the UI
+     * visibility of the window like the owner activity of the dialog and then (after showing it)
+     * makes the dialog focusable again.
+     */
+    @Override
+    public void show() {
+        // Set the dialog to not focusable.
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+
+        copySystemUiVisibility();
+
+        // Show the dialog with NavBar hidden.
+        super.show();
+
+        // Set the dialog to focusable again.
+        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+    }
+
+    /**
+     * Copy the visibility of the Activity that has started the dialog {@link mContext}. If the
+     * activity is in Immersive mode the dialog will be in Immersive mode too and vice versa.
+     */
+    private void copySystemUiVisibility() {
+        getWindow().getDecorView().setSystemUiVisibility(
+                ((Activity) mContext).getWindow().getDecorView().getSystemUiVisibility()
+        );
+    }
+
     void setContent(View contentView) {
         mContent = contentView;
     }
 
-    void setOnDismissListener(AppStyledDismissListener listener) {
+    void setOnDismissListener(Runnable listener) {
         mOnDismissListener = listener;
     }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialogController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialogController.java
index 87e4d73..ff1089e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialogController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledDialogController.java
@@ -16,22 +16,27 @@
 
 package com.android.car.ui.appstyledview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledDismissListener;
-import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledVCloseClickListener;
 import com.android.car.ui.appstyledview.AppStyledViewController.AppStyledViewNavIcon;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import java.util.Objects;
 
 /**
  * Controller to interact with the app styled view UI.
+ * <p>
+ * Rendered views will comply with
+ * <a href="https://source.android.com/devices/automotive/hmi/car_ui/appendix_b">customization guardrails</a>
  */
+@TargetApi(MIN_TARGET_API)
 public final class AppStyledDialogController {
 
     @NonNull
@@ -41,7 +46,7 @@
 
     public AppStyledDialogController(@NonNull Context context) {
         Objects.requireNonNull(context);
-        mAppStyledViewController = SharedLibraryFactorySingleton.get(context)
+        mAppStyledViewController = PluginFactorySingleton.get(context)
                 .createAppStyledView(context);
         mDialog = new AppStyledDialog(context, mAppStyledViewController);
     }
@@ -59,7 +64,6 @@
         Objects.requireNonNull(contentView);
 
         mDialog.setContent(contentView);
-        mAppStyledViewController.setOnCloseClickListener(mDialog::dismiss);
     }
 
     /**
@@ -77,19 +81,24 @@
     }
 
     /**
-     * Sets the {@link AppStyledVCloseClickListener}
+     * Dismiss this dialog, removing it from the screen. This method can be invoked safely from any
+     * thread.
      */
-    public void setOnCloseClickListener(@NonNull AppStyledVCloseClickListener listener) {
-        mAppStyledViewController.setOnCloseClickListener(() -> {
-            mDialog.dismiss();
-            listener.onClick();
-        });
+    public void dismiss() {
+        mDialog.dismiss();
     }
 
     /**
-     * Sets the {@link AppStyledDismissListener}
+     * Sets a runnable that will be invoked when a nav icon is clicked.
      */
-    public void setOnDismissListener(@NonNull AppStyledDismissListener listener) {
+    public void setOnNavIconClickListener(@NonNull Runnable listener) {
+        mAppStyledViewController.setOnNavIconClickListener(listener);
+    }
+
+    /**
+     * Sets a runnable that will be invoked when a dialog is dismissed.
+     */
+    public void setOnDismissListener(@NonNull Runnable listener) {
         mDialog.setOnDismissListener(listener);
     }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledRecyclerViewAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledRecyclerViewAdapter.java
index ed2f1b8..e4ae1c4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledRecyclerViewAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledRecyclerViewAdapter.java
@@ -22,6 +22,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
@@ -33,9 +34,10 @@
 public class AppStyledRecyclerViewAdapter extends
         RecyclerView.Adapter<AppStyledRecyclerViewHolder> {
 
-    private View mContent;
+    @Nullable
+    private final View mContent;
 
-    public AppStyledRecyclerViewAdapter(View content) {
+    public AppStyledRecyclerViewAdapter(@Nullable View content) {
         mContent = content;
     }
 
@@ -55,7 +57,7 @@
 
     @Override
     public int getItemCount() {
-        return 1;
+        return mContent != null ? 1 : 0;
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewController.java
index 7280773..fc6241e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewController.java
@@ -22,6 +22,7 @@
 import android.view.WindowManager;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
 
 import java.lang.annotation.Retention;
 
@@ -32,28 +33,6 @@
 public interface AppStyledViewController {
 
     /**
-     * Callback to be invoked when the close icon is clicked.
-     */
-    interface AppStyledVCloseClickListener {
-
-        /**
-         * Called when the close icon is clicked.
-         */
-        void onClick();
-    }
-
-    /**
-     * Callback to be invoked when the dialog is dismissed.
-     */
-    interface AppStyledDismissListener {
-
-        /**
-         * Called when the dialog is dismissed.
-         */
-        void onDismiss();
-    }
-
-    /**
      * The possible values for AppStyledViewNavIcon.
      */
     @IntDef({
@@ -79,7 +58,7 @@
      *
      * @return the view used for app styled view.
      */
-    View getAppStyledView(View contentView);
+    View getAppStyledView(@Nullable View contentView);
 
     /**
      * Sets the nav icon to be used.
@@ -87,9 +66,9 @@
     void setNavIcon(@AppStyledViewNavIcon int navIcon);
 
     /**
-     * Sets the {@link AppStyledVCloseClickListener}
+     * Sets a runnable that will be invoked when a nav icon is clicked.
      */
-    void setOnCloseClickListener(AppStyledVCloseClickListener listener);
+    void setOnNavIconClickListener(Runnable listener);
 
     /**
      * Returns the layout params for the AppStyledView dialog
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerAdapterV1.java
index 0422b0b..24a9307 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerAdapterV1.java
@@ -19,8 +19,9 @@
 import android.view.WindowManager.LayoutParams;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
-import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
 
 /**
  * Adapts a {@link AppStyledViewControllerOEMV1} into a {@link AppStyledViewController}
@@ -30,26 +31,37 @@
     @NonNull
     private final AppStyledViewControllerOEMV1 mOemController;
 
-    public AppStyledViewControllerAdapterV1(AppStyledViewControllerOEMV1 controllerOEMV1) {
+    public AppStyledViewControllerAdapterV1(@NonNull AppStyledViewControllerOEMV1 controllerOEMV1) {
         mOemController = controllerOEMV1;
+        mOemController.setNavIcon(AppStyledViewControllerOEMV1.NAV_ICON_CLOSE);
     }
 
     /**
      * Returns the view that will be displayed on the screen.
      */
     @Override
-    public View getAppStyledView(View contentView) {
-        return mOemController.getAppStyledView(contentView);
+    public View getAppStyledView(@Nullable View contentView) {
+        mOemController.setContent(contentView);
+        return mOemController.getView();
     }
 
     @Override
-    public void setNavIcon(int navIcon) {
-        mOemController.setNavIcon(navIcon);
+    public void setNavIcon(@AppStyledViewNavIcon int navIcon) {
+        switch (navIcon) {
+            case AppStyledViewNavIcon.BACK:
+                mOemController.setNavIcon(AppStyledViewControllerOEMV1.NAV_ICON_BACK);
+                break;
+            case AppStyledViewNavIcon.CLOSE:
+                mOemController.setNavIcon(AppStyledViewControllerOEMV1.NAV_ICON_CLOSE);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown nav icon style: " + navIcon);
+        }
     }
 
     @Override
-    public void setOnCloseClickListener(AppStyledVCloseClickListener listener) {
-        mOemController.setOnCloseClickListener(listener::onClick);
+    public void setOnNavIconClickListener(Runnable listener) {
+        mOemController.setOnBackClickListener(listener);
     }
 
     @Override
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerImpl.java
index 50ded45..8c3bfbe 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/appstyledview/AppStyledViewControllerImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.appstyledview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.util.DisplayMetrics;
@@ -28,6 +31,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -37,6 +41,7 @@
 /**
  * Controller to interact with the app styled view.
  */
+@TargetApi(MIN_TARGET_API)
 public class AppStyledViewControllerImpl implements AppStyledViewController {
 
     private static final double VISIBLE_SCREEN_PERCENTAGE = 0.9;
@@ -44,7 +49,7 @@
     private final Context mContext;
     @AppStyledViewNavIcon
     private int mAppStyleViewNavIcon;
-    private AppStyledVCloseClickListener mAppStyledVCloseClickListener = null;
+    private Runnable mAppStyledVCloseClickListener = null;
 
     public AppStyledViewControllerImpl(Context context) {
         mContext = context;
@@ -59,7 +64,7 @@
      * Sets the AppStyledVCloseClickListener on the close icon.
      */
     @Override
-    public void setOnCloseClickListener(AppStyledVCloseClickListener listener) {
+    public void setOnNavIconClickListener(Runnable listener) {
         mAppStyledVCloseClickListener = listener;
     }
 
@@ -106,7 +111,7 @@
     }
 
     @Override
-    public View getAppStyledView(View contentView) {
+    public View getAppStyledView(@Nullable View contentView) {
         // create ContextThemeWrapper from the original Activity Context with the custom theme
         final Context contextThemeWrapper = new ContextThemeWrapper(mContext, R.style.Theme_CarUi);
         LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -137,7 +142,7 @@
                 appStyleView.findViewById(R.id.car_ui_app_styled_view_nav_icon_container);
         if (mAppStyledVCloseClickListener != null && navContainer != null) {
             navContainer.setOnClickListener((v) -> {
-                mAppStyledVCloseClickListener.onClick();
+                mAppStyledVCloseClickListener.run();
             });
         }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java
index 181495c..d4778ef 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/ClickBlockingView.java
@@ -15,6 +15,10 @@
  */
 package com.android.car.ui.baselayout;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -27,6 +31,8 @@
  *
  * <p>Used in baselayouts to prevent clicking through the toolbar.
  */
+@SuppressLint("ClickableViewAccessibility")
+@TargetApi(MIN_TARGET_API)
 public class ClickBlockingView extends View {
 
     private boolean mEatingTouch = false;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java
index e45a4b6..b7f24e4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/baselayout/Insets.java
@@ -16,6 +16,10 @@
 
 package com.android.car.ui.baselayout;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
+
 import java.util.Objects;
 
 /**
@@ -24,6 +28,7 @@
  *
  * See {@link InsetsChangedListener} for more information.
  */
+@TargetApi(MIN_TARGET_API)
 public final class Insets {
     private final int mLeft;
     private final int mRight;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
index 3d415dc..861f45f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/BaseLayoutController.java
@@ -15,10 +15,12 @@
  */
 package com.android.car.ui.core;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+import static com.android.car.ui.utils.CarUiUtils.getThemeBoolean;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
-import android.content.res.TypedArray;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -29,7 +31,7 @@
 import com.android.car.ui.R;
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.toolbar.ToolbarController;
 
 import java.util.Map;
@@ -40,6 +42,7 @@
  * It also exposes a {@link ToolbarController} to access the toolbar. This may be null if
  * used with a base layout without a Toolbar.
  */
+@TargetApi(MIN_TARGET_API)
 public final class BaseLayoutController {
 
     private static final Map<Activity, BaseLayoutController> sBaseLayoutMap = new WeakHashMap<>();
@@ -117,7 +120,7 @@
                 requireViewByRefId(activity.getWindow().getDecorView(), android.R.id.content);
 
         mInsetsUpdater = new InsetsUpdater(activity, contentView);
-        mToolbarController = SharedLibraryFactorySingleton.get(activity)
+        mToolbarController = PluginFactorySingleton.get(activity)
                 .installBaseLayoutAround(
                         contentView,
                         mInsetsUpdater,
@@ -126,20 +129,6 @@
     }
 
     /**
-     * Gets the boolean value of an Attribute from an {@link Activity Activity's}
-     * {@link android.content.res.Resources.Theme}.
-     */
-    private static boolean getThemeBoolean(Activity activity, int attr) {
-        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
-
-        try {
-            return a.getBoolean(0, false);
-        } finally {
-            a.recycle();
-        }
-    }
-
-    /**
      * InsetsUpdater waits for layout changes, and when there is one, calculates the appropriate
      * insets into the content view.
      *
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
index a780498..3f999cb 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUi.java
@@ -16,9 +16,12 @@
 package com.android.car.ui.core;
 
 import static com.android.car.ui.core.BaseLayoutController.getBaseLayoutController;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
+import android.os.Build.VERSION_CODES;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -28,8 +31,8 @@
 import com.android.car.ui.R;
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 import com.android.car.ui.recyclerview.CarUiListItem;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
 import com.android.car.ui.toolbar.ToolbarController;
 
 import java.util.List;
@@ -38,20 +41,26 @@
 /**
  * Public interface for general CarUi static functions.
  */
+@TargetApi(MIN_TARGET_API)
 public class CarUi {
 
+    // Unfortunately, because some of our clients don't have a car specific build we can't set the
+    // minSdk to 28. so we need to enforce minSdk to 28 in the code.
+    public static final int MIN_TARGET_API = VERSION_CODES.P;
+    public static final int TARGET_API_R = VERSION_CODES.R;
+
     /** Prevent instantiating this class */
     private CarUi() {}
 
     /**
-     * Gets a CarUi component, such as {@link com.android.car.ui.button.CarUiButton}, from the
+     * Gets a CarUi component, such as {@link com.android.car.ui.widget.CarUiTextView}, from the
      * view hierarchy. The interfaces for these components don't extend View, so you can't
      * get them through findViewById().
      *
      * @param view The parent view. Its descendants will be searched for the component.
      * @param id The id of the component.
      * @param <T> The resulting type of the component, such as
-     *            {@link com.android.car.ui.button.CarUiButton}
+     *            {@link com.android.car.ui.widget.CarUiTextView}
      * @return The component found, or null.
      */
     @Nullable
@@ -71,6 +80,7 @@
      * if the result is null.
      */
     @NonNull
+    @SuppressWarnings("TypeParameterUnusedInFormals")
     public static <T> T requireCarUiComponentById(View view, int id) {
         return Objects.requireNonNull(findCarUiComponentById(view, id));
     }
@@ -96,7 +106,7 @@
      */
     public static RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter(
             Context context, List<? extends CarUiListItem> items) {
-        return SharedLibraryFactorySingleton.get(context).createListItemAdapter(items);
+        return PluginFactorySingleton.get(context).createListItemAdapter(items);
     }
 
 
@@ -217,7 +227,7 @@
             InsetsChangedListener insetsChangedListener,
             boolean hasToolbar,
             boolean fullscreen) {
-        return SharedLibraryFactorySingleton.get(view.getContext())
+        return PluginFactorySingleton.get(view.getContext())
                 .installBaseLayoutAround(view, insetsChangedListener, hasToolbar, fullscreen);
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
index 566502b..d47dddd 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/core/CarUiInstaller.java
@@ -15,29 +15,35 @@
  */
 package com.android.car.ui.core;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Application;
+import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.appcompat.app.AppCompatDelegate;
 
 import com.android.car.ui.CarUiLayoutInflaterFactory;
+import com.android.car.ui.R;
 import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.utils.CarUiUtils;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.Arrays;
-import java.util.Locale;
+import java.util.HashSet;
 
 /**
  * {@link ContentProvider ContentProvider's} onCreate() methods are "called for all registered
@@ -54,6 +60,9 @@
  * {@link BaseLayoutController} class otherwise the base layout will be loaded
  * by the wrong classloader. And then calls to {@see CarUi#getToolbar(Activity)} will return null.
  */
+// TODO: (b/200322953)
+@SuppressLint("LogConditional")
+@RequiresApi(MIN_TARGET_API)
 public class CarUiInstaller extends ContentProvider {
 
     private static final String TAG = "CarUiInstaller";
@@ -62,27 +71,59 @@
     private static final String CAR_UI_INSET_TOP = "CAR_UI_INSET_TOP";
     private static final String CAR_UI_INSET_BOTTOM = "CAR_UI_INSET_BOTTOM";
 
-    private static final boolean IS_DEBUG_DEVICE =
-            Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
-                    || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
+    // applications against which we have already called register
+    private static final HashSet<Application> sAppsRegistered = new HashSet<Application>();
+
+    private static boolean hasAlreadyRegistered(Application application) {
+        synchronized (sAppsRegistered) {
+            return !sAppsRegistered.add(application);
+        }
+    }
 
     @Override
     public boolean onCreate() {
         Context context = getContext();
         if (context == null || !(context.getApplicationContext() instanceof Application)) {
-            Log.e(TAG, "CarUiInstaller had a null context!");
+            Log.e(TAG, "CarUiInstaller had a null context, unable to call register!"
+                        + " Need app to call register by itself");
             return false;
         }
-        Log.i(TAG, "CarUiInstaller started for " + context.getPackageName());
 
         Application application = (Application) context.getApplicationContext();
+        register(application);
+
+        return true;
+    }
+
+    /**
+     * In some cases {@link CarUiInstaller#onCreate} is called before the {@link Application}
+     * instance is created. In those cases applications have to call this method separately
+     * after the Application instance is fully initialized.
+     */
+    public static void register(@NonNull Application application) {
+        if (hasAlreadyRegistered(application)) {
+            return;
+        }
+
         application.registerActivityLifecycleCallbacks(
                 new Application.ActivityLifecycleCallbacks() {
                     private Insets mInsets = null;
                     private boolean mIsActivityStartedForFirstTime = false;
 
+                    private boolean shouldRun(Activity activity) {
+                        return CarUiUtils.getThemeBoolean(activity, R.attr.carUiActivity);
+                    }
+
                     @Override
                     public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                        if (!shouldRun(activity)) {
+                            return;
+                        }
+
+                        ComponentName comp = ComponentName.createRelative(
+                                activity, activity.getClass().getName());
+                        Log.i(TAG, "CarUiInstaller started for " + comp.flattenToShortString());
+
                         injectLayoutInflaterFactory(activity);
 
                         callMethodReflective(
@@ -105,6 +146,10 @@
 
                     @Override
                     public void onActivityPostStarted(Activity activity) {
+                        if (!shouldRun(activity)) {
+                            return;
+                        }
+
                         Object controller = callMethodReflective(
                                 activity.getClassLoader(),
                                 BaseLayoutController.class,
@@ -141,6 +186,10 @@
 
                     @Override
                     public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+                        if (!shouldRun(activity)) {
+                            return;
+                        }
+
                         Object controller = callMethodReflective(
                                 activity.getClassLoader(),
                                 BaseLayoutController.class,
@@ -182,6 +231,10 @@
 
                     @Override
                     public void onActivityDestroyed(Activity activity) {
+                        if (!shouldRun(activity)) {
+                            return;
+                        }
+
                         callMethodReflective(
                                 activity.getClassLoader(),
                                 BaseLayoutController.class,
@@ -190,8 +243,6 @@
                                 activity);
                     }
                 });
-
-        return true;
     }
 
     @Nullable
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java
index 3733dcb..46afdac 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeSearchListItem.java
@@ -24,8 +24,9 @@
 import com.android.car.ui.recyclerview.CarUiContentListItem;
 
 /**
- * Definition of list items that can be inserted into {@link CarUiListItemAdapter}. This class is
- * used to display the search items in the template for wide screen mode.
+ * Definition of list items that can be inserted into
+ * {@link com.android.car.ui.recyclerview.CarUiListItemAdapter}. This class is used to display the
+ * search items in the template for wide screen mode.
  *
  * The class is used to pass application icon resources ids to the IME for rendering in its
  * process. Applications can also pass a unique id for each item and supplemental icon that will be
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
index 286d621..097009d 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/imewidescreen/CarUiImeWideScreenController.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.imewidescreen;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
+
+import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -84,8 +87,8 @@
  *      from {@link InputMethodService#onCreateInputView()}</li>
  *      <li>{@link #onComputeInsets(InputMethodService.Insets) should be called from
  *      {@link InputMethodService#onComputeInsets(InputMethodService.Insets)}</li>
- *      <li>{@link #onAppPrivateCommand(String, Bundle) should be called from {
- *      @link InputMethodService#onAppPrivateCommand(String, Bundle)}}</li>
+ *      <li>{@link #onAppPrivateCommand(String, Bundle) should be called from
+ *      {@link InputMethodService#onAppPrivateCommand(String, Bundle)}}</li>
  *      <li>{@link #setExtractViewShown(boolean)} should be called from
  *      {@link InputMethodService#setExtractViewShown(boolean)}</li>
  *      <li>{@link #onStartInputView(EditorInfo, InputConnection, CharSequence)} should be called
@@ -100,6 +103,7 @@
  * {@link InputMethodService#setExtractViewShown(boolean)} and return the original value instead
  * of false. for more info see {@link #setExtractViewShown(boolean)}
  */
+@RequiresApi(TARGET_API_R)
 public class CarUiImeWideScreenController {
 
     private static final String TAG = "ImeWideScreenController";
@@ -112,6 +116,9 @@
     // Action name of action that will be used by IMS to notify the application to clear the data
     // in the EditText.
     public static final String WIDE_SCREEN_CLEAR_DATA_ACTION = "automotive_wide_screen_clear_data";
+    // Action name when user clicks on the back button to close the IME.
+    public static final String WIDE_SCREEN_ON_BACK_CLICKED_ACTION =
+            "automotive_wide_screen_back_clicked";
     public static final String WIDE_SCREEN_POST_LOAD_SEARCH_RESULTS_ACTION =
             "automotive_wide_screen_post_load_search_results";
     // Action name used by applications to notify that new search results are available.
@@ -247,7 +254,7 @@
 
             @Override
             public void surfaceChanged(SurfaceHolder holder, int format,
-                                       int width, int height) {
+                    int width, int height) {
                 Bundle bundle = new Bundle();
                 bundle.putInt(CONTENT_AREA_SURFACE_HEIGHT,
                         mContentAreaSurfaceView.getHeight());
@@ -327,12 +334,12 @@
      * description and not the title.
      * <p>
      * When the IME window is closed all the views are reset. For the default view visibility see
-     * {@link #resetAutomotiveWideScreenViews()}.
+     * {@code resetAutomotiveWideScreenViews}.
      *
      * @param action Name of the command to be performed.
-     * @param data   Any data to include with the command.
+     * @param data Any data to include with the command.
      */
-    @RequiresApi(api = VERSION_CODES.R)
+    @RequiresApi(TARGET_API_R)
     public void onAppPrivateCommand(String action, Bundle data) {
         if (!isWideScreenMode()) {
             return;
@@ -432,6 +439,7 @@
         }
     }
 
+    @SuppressLint("Range")
     private void loadSearchItems() {
         if (mInputEditorInfo == null) {
             Log.w(TAG, "Result can't be loaded, input InputEditorInfo not available ");
@@ -517,10 +525,10 @@
      * Initialize the view in the wide screen template based on the data provided by the app through
      * {@link #onAppPrivateCommand(String, Bundle)}
      */
-    @RequiresApi(api = VERSION_CODES.R)
+    @RequiresApi(TARGET_API_R)
     public void onStartInputView(@NonNull EditorInfo editorInfo,
-                                 @Nullable InputConnection inputConnection,
-                                 @Nullable CharSequence textForImeAction) {
+            @Nullable InputConnection inputConnection,
+            @Nullable CharSequence textForImeAction) {
         if (!isWideScreenMode()) {
             return;
         }
@@ -563,6 +571,8 @@
             close.setOnClickListener(
                     (v) -> {
                         mInputMethodService.requestHideSelf(0);
+                        mInputConnection.performPrivateCommand(WIDE_SCREEN_ON_BACK_CLICKED_ACTION,
+                                null);
                     });
         }
 
@@ -600,7 +610,7 @@
      * information will ONLY be sent if OEM allows an application to hide the content area and let
      * it draw its own content.
      */
-    @RequiresApi(api = VERSION_CODES.R)
+    @RequiresApi(TARGET_API_R)
     private void sendSurfaceInfo() {
         if (!mAllowAppToHideContentArea && mContentAreaSurfaceView.getDisplay() == null
                 && !(mInputEditorInfo != null
@@ -609,10 +619,9 @@
         }
         // Dispatch the window visibility change for IME window as soon as its displayed.
         mRootView.dispatchWindowVisibilityChanged(View.VISIBLE);
-        IBinder hostToken = null;
         int displayId = mContentAreaSurfaceView.getDisplay() == null
                 ? 0 : mContentAreaSurfaceView.getDisplay().getDisplayId();
-        hostToken = mContentAreaSurfaceView.getHostToken();
+        IBinder hostToken = mContentAreaSurfaceView.getHostToken();
 
         Bundle bundle = new Bundle();
         bundle.putBinder(CONTENT_AREA_SURFACE_HOST_TOKEN, hostToken);
@@ -651,7 +660,7 @@
      */
     @Nullable
     private static PackageInfo getPackageInfo(Context context,
-                                              String packageName) {
+            String packageName) {
         PackageManager packageManager = context.getPackageManager();
         PackageInfo packageInfo = null;
         try {
@@ -694,7 +703,7 @@
      * <p>
      * For example, within the IMS service call
      * <pre>
-     *   @Override
+     *   &#64;Override
      *   public void setExtractViewShown(boolean shown) {
      *     if (!carUiImeWideScreenController.isWideScreenMode()) {
      *         super.setExtractViewShown(shown);
@@ -804,7 +813,7 @@
     /**
      * Called when IME window closes. Reset all the views once that happens.
      */
-    @RequiresApi(api = VERSION_CODES.R)
+    @RequiresApi(TARGET_API_R)
     public void onFinishInputView() {
         if (!isWideScreenMode()) {
             return;
@@ -812,7 +821,7 @@
         resetAutomotiveWideScreenViews();
     }
 
-    @RequiresApi(api = VERSION_CODES.R)
+    @RequiresApi(TARGET_API_R)
     private void resetAutomotiveWideScreenViews() {
         mWideScreenDescriptionTitle.setVisibility(View.GONE);
         mContentAreaSurfaceView.setVisibility(View.GONE);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/AdapterClassLoader.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/AdapterClassLoader.java
similarity index 86%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/AdapterClassLoader.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/AdapterClassLoader.java
index 7995c42..096e757 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/AdapterClassLoader.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/AdapterClassLoader.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -34,16 +34,15 @@
 class AdapterClassLoader extends PathClassLoader {
 
     @Nullable
-    private final ClassLoader mSharedLibraryClassLoader;
+    private final ClassLoader mPluginClassLoader;
 
     private static final Pattern PATTERN = Pattern.compile(
             "^com\\.android\\.car\\.ui\\..*AdapterV[0-9]+(\\$.*)?$"
             + "|Lambda"
             + "|^" + Pattern.quote(OemApiUtil.class.getName()) + "$");
 
-    private static final Pattern SHARED_LIBRARY_PATTERN =
-            Pattern.compile("^com\\.android\\.car\\.ui\\.sharedlibrary\\."
-                    + "(oemapis\\..*|SharedLibraryVersionProviderImpl)$");
+    private static final Pattern PLUGIN_PATTERN = Pattern.compile(
+            "^com\\.android\\.car\\.ui\\.plugin\\.(oemapis\\..*|PluginVersionProviderImpl)$");
 
     /**
      * Equivalent to calling {@link #AdapterClassLoader(String, String, ClassLoader, ClassLoader)}
@@ -72,7 +71,7 @@
             @Nullable ClassLoader parent, @Nullable ClassLoader additionalClassloader,
                 boolean delegateResourceLoading) {
         super(dexPath, librarySearchPath, parent);
-        mSharedLibraryClassLoader = additionalClassloader;
+        mPluginClassLoader = additionalClassloader;
     }
 
     /**
@@ -102,8 +101,6 @@
 
         // Only load adapter classes and certain util classes in this classloader.
         if (name != null && PATTERN.matcher(name).find()) {
-            // Next, check whether the class in question is present in the dexPath that this
-            // classloader operates on, or its shared libraries.
             try {
                 return findClass(name);
             } catch (ClassNotFoundException ex) {
@@ -111,11 +108,10 @@
             }
         }
 
-        // Loading OEM-APIs
-        // Next, check any additional classloaders.
-        if (mSharedLibraryClassLoader != null && SHARED_LIBRARY_PATTERN.matcher(name).matches()) {
+        // Load only OEM-APIs from the plugin classloader
+        if (mPluginClassLoader != null && PLUGIN_PATTERN.matcher(name).matches()) {
             try {
-                return mSharedLibraryClassLoader.loadClass(name);
+                return mPluginClassLoader.loadClass(name);
             } catch (ClassNotFoundException ignored) {
                 // It's fine, just continue
             }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/OemApiUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/OemApiUtil.java
new file mode 100644
index 0000000..1d6166f
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/OemApiUtil.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 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.ui.pluginsupport;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV2;
+import com.android.car.ui.plugin.oemapis.PluginVersionProviderOEMV1;
+
+/**
+ * Helper class for accessing oem-apis without reflection.
+ *
+ * Because this class creates adapters, and adapters implement OEM APIs, this class cannot
+ * be created with the app's classloader. You must use {@link AdapterClassLoader} to load it
+ * so that both the app and plugin's classes can be loaded from this class.
+ */
+@RequiresApi(MIN_TARGET_API)
+final class OemApiUtil {
+
+    private static final String TAG = "carui";
+
+    /**
+     * Given a plugin's context, return it's implementation of {@link PluginFactory}.
+     *
+     * This is done by asking the plugin's {@link PluginVersionProvider} for a
+     * factory object, and checking if it's instanceof each version of
+     * {@link PluginFactoryOEMV1}, casting to the correct one when found.
+     *
+     * @param pluginContext The plugin's context. This context will return
+     *                             the plugin's classloader from
+     *                             {@link Context#getClassLoader()}.
+     * @param appPackageName The package name of the application. This is passed to the plugin
+     *                       so that it can provide unique customizations per-app.
+     * @return A {@link PluginFactory}
+     */
+    @SuppressLint("PrivateApi")
+    static PluginFactory getPluginFactory(
+            Context pluginContext, String appPackageName) {
+
+        Object oemVersionProvider = null;
+        try {
+            oemVersionProvider = Class
+                    .forName("com.android.car.ui.plugin.PluginVersionProviderImpl")
+                    .getDeclaredConstructor()
+                    .newInstance();
+        } catch (ClassNotFoundException e) {
+            Log.i(TAG, "PluginVersionProviderImpl not found.", e);
+        } catch (ReflectiveOperationException e) {
+            Log.e(TAG, "PluginVersionProviderImpl could not be instantiated!", e);
+        }
+
+        // Add new version providers in an if-else chain here, in descending version order so
+        // that higher versions are preferred.
+        PluginVersionProvider versionProvider = null;
+        if (oemVersionProvider instanceof PluginVersionProviderOEMV1) {
+            versionProvider = new PluginVersionProviderAdapterV1(
+                    (PluginVersionProviderOEMV1) oemVersionProvider);
+        } else {
+            Log.e(TAG, "PluginVersionProviderImpl was not instanceof any known "
+                    + "versions of PluginVersionProviderOEMV#.");
+        }
+
+        PluginFactory oemPluginFactory = null;
+        if (versionProvider != null) {
+            Object factory = versionProvider.getPluginFactory(
+                    1, pluginContext, appPackageName);
+            if (factory instanceof PluginFactoryOEMV1) {
+                oemPluginFactory = new PluginFactoryAdapterV1(
+                        (PluginFactoryOEMV1) factory);
+            } else if (classExists(
+                    "com.android.car.ui.plugin.oemapis.PluginFactoryOEMV2")
+                    && factory instanceof PluginFactoryOEMV2) {
+                oemPluginFactory = new PluginFactoryAdapterV2(
+                        (PluginFactoryOEMV2) factory);
+            } else {
+                Log.e(TAG, "PluginVersionProvider found, but did not provide a"
+                        + " factory implementing any known interfaces!");
+            }
+        }
+
+        return oemPluginFactory;
+    }
+
+    private static boolean classExists(@Nullable String className) {
+        if (className == null) {
+            return false;
+        }
+        try {
+            Class.forName(className);
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    private OemApiUtil() {}
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryConfigProvider.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginConfigProvider.java
similarity index 77%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryConfigProvider.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginConfigProvider.java
index 239be64..4a9290c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryConfigProvider.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginConfigProvider.java
@@ -13,20 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
 import java.util.Set;
 
 /**
  * This interface can be implemented on an app's {@link android.app.Application} class
- * in order to provide additional options to consider when loading the shared library.
+ * in order to provide additional options to consider when loading the plugin.
  */
-public interface SharedLibraryConfigProvider {
+public interface PluginConfigProvider {
 
     /**
-     * Returns a set of {@link SharedLibrarySpecifier SharedLibrarySpecifiers}. If a shared library
+     * Returns a set of {@link PluginSpecifier PluginSpecifiers}. If a plugin
      * is in this set, it will not be loaded. The fallback implementation in the static version of
      * car-ui-lib will be used instead.
      */
-    Set<SharedLibrarySpecifier> getSharedLibraryDenyList();
+    Set<PluginSpecifier> getPluginDenyList();
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactory.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactory.java
similarity index 96%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactory.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactory.java
index 8010341..2d2815b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactory.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactory.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -35,7 +35,7 @@
 /**
  * This interface contains methods to create customizable carui components.
  */
-public interface SharedLibraryFactory {
+public interface PluginFactory {
 
     /**
      * Creates the base layout, and optionally the toolbar.
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV1.java
new file mode 100644
index 0000000..b27675b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV1.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 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.ui.pluginsupport;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusAreaAdapterV1;
+import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.FocusParkingViewAdapterV1;
+import com.android.car.ui.appstyledview.AppStyledViewController;
+import com.android.car.ui.appstyledview.AppStyledViewControllerAdapterV1;
+import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
+import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.plugin.oemapis.InsetsOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV1;
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.recyclerview.CarUiListItem;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.toolbar.ToolbarController;
+import com.android.car.ui.toolbar.ToolbarControllerAdapterV1;
+import com.android.car.ui.widget.CarUiTextView;
+
+import java.util.List;
+
+/**
+ * This class is an wrapper around {@link PluginFactoryOEMV1} that implements {@link
+ * PluginFactory}, to provide a version-agnostic way of interfacing with the OEM's
+ * PluginFactory.
+ */
+@TargetApi(MIN_TARGET_API)
+public final class PluginFactoryAdapterV1 implements PluginFactory {
+    @NonNull
+    private final PluginFactoryOEMV1 mOem;
+    @NonNull
+    private final PluginFactoryStub mFactoryStub = new PluginFactoryStub();
+
+    public PluginFactoryAdapterV1(@NonNull PluginFactoryOEMV1 oem) {
+        mOem = oem;
+
+        mOem.setRotaryFactories(
+                c -> new FocusParkingViewAdapterV1(new FocusParkingView(c)),
+                c -> new FocusAreaAdapterV1(new FocusArea(c)));
+    }
+
+    @Override
+    @Nullable
+    public ToolbarController installBaseLayoutAround(
+            View contentView,
+            InsetsChangedListener insetsChangedListener,
+            boolean toolbarEnabled,
+            boolean fullscreen) {
+
+        if (!mOem.customizesBaseLayout()) {
+            return mFactoryStub.installBaseLayoutAround(contentView,
+                    insetsChangedListener, toolbarEnabled, fullscreen);
+        }
+
+        ToolbarControllerOEMV1 toolbar = mOem.installBaseLayoutAround(
+                contentView.getContext(),
+                contentView,
+                insets -> insetsChangedListener.onCarUiInsetsChanged(adaptInsets(insets)),
+                toolbarEnabled, fullscreen);
+
+        return toolbar != null
+                ? new ToolbarControllerAdapterV1(contentView.getContext(), toolbar)
+                : null;
+    }
+
+    @NonNull
+    @Override
+    public CarUiTextView createTextView(Context context, AttributeSet attrs) {
+        return mFactoryStub.createTextView(context, attrs);
+    }
+
+
+    @Override
+    public AppStyledViewController createAppStyledView(Context activityContext) {
+        AppStyledViewControllerOEMV1 appStyledViewControllerOEMV1 = mOem.createAppStyledView(
+                activityContext);
+        return appStyledViewControllerOEMV1 == null ? new AppStyledViewControllerImpl(
+                activityContext) : new AppStyledViewControllerAdapterV1(
+                appStyledViewControllerOEMV1);
+    }
+
+    private Insets adaptInsets(InsetsOEMV1 insetsOEM) {
+        return new Insets(insetsOEM.getLeft(), insetsOEM.getTop(),
+                insetsOEM.getRight(), insetsOEM.getBottom());
+    }
+
+    @Override
+    public CarUiRecyclerView createRecyclerView(Context context, AttributeSet attrs) {
+        return mFactoryStub.createRecyclerView(context, attrs);
+    }
+
+    @Override
+    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter(
+            List<? extends CarUiListItem> items) {
+        return mFactoryStub.createListItemAdapter(items);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV2.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV2.java
new file mode 100644
index 0000000..d8c05d7
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryAdapterV2.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2020 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.ui.pluginsupport;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.text.SpannableString;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.CarUiText;
+import com.android.car.ui.FocusArea;
+import com.android.car.ui.FocusAreaAdapterV1;
+import com.android.car.ui.FocusParkingView;
+import com.android.car.ui.FocusParkingViewAdapterV1;
+import com.android.car.ui.R;
+import com.android.car.ui.appstyledview.AppStyledViewController;
+import com.android.car.ui.appstyledview.AppStyledViewControllerAdapterV1;
+import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
+import com.android.car.ui.baselayout.Insets;
+import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.plugin.oemapis.InsetsOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV2;
+import com.android.car.ui.plugin.oemapis.TextOEMV1;
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ContentListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.HeaderListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.LayoutStyleOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.recyclerview.CarUiContentListItem;
+import com.android.car.ui.recyclerview.CarUiHeaderListItem;
+import com.android.car.ui.recyclerview.CarUiLayoutStyle;
+import com.android.car.ui.recyclerview.CarUiListItem;
+import com.android.car.ui.recyclerview.CarUiListItemAdapterAdapterV1;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout;
+import com.android.car.ui.recyclerview.RecyclerViewAdapterV1;
+import com.android.car.ui.toolbar.ToolbarController;
+import com.android.car.ui.toolbar.ToolbarControllerAdapterV1;
+import com.android.car.ui.utils.CarUiUtils;
+import com.android.car.ui.widget.CarUiTextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * This class is an wrapper around {@link PluginFactoryOEMV1} that implements {@link PluginFactory},
+ * to provide a version-agnostic way of interfacing with the OEM's PluginFactory.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
+public final class PluginFactoryAdapterV2 implements PluginFactory {
+    @NonNull
+    private final PluginFactoryOEMV2 mOem;
+    @NonNull
+    private final PluginFactoryStub mFactoryStub = new PluginFactoryStub();
+
+    public PluginFactoryAdapterV2(@NonNull PluginFactoryOEMV2 oem) {
+        mOem = oem;
+
+        mOem.setRotaryFactories(
+                c -> new FocusParkingViewAdapterV1(new FocusParkingView(c)),
+                c -> new FocusAreaAdapterV1(new FocusArea(c)));
+    }
+
+    @Override
+    @Nullable
+    public ToolbarController installBaseLayoutAround(
+            View contentView,
+            InsetsChangedListener insetsChangedListener,
+            boolean toolbarEnabled,
+            boolean fullscreen) {
+
+        if (!mOem.customizesBaseLayout()) {
+            return mFactoryStub.installBaseLayoutAround(contentView,
+                    insetsChangedListener, toolbarEnabled, fullscreen);
+        }
+
+        ToolbarControllerOEMV1 toolbar = mOem.installBaseLayoutAround(
+                contentView.getContext(),
+                contentView,
+                insets -> insetsChangedListener.onCarUiInsetsChanged(adaptInsets(insets)),
+                toolbarEnabled, fullscreen);
+
+        return toolbar != null
+                ? new ToolbarControllerAdapterV1(contentView.getContext(), toolbar)
+                : null;
+    }
+
+    @NonNull
+    @Override
+    public CarUiTextView createTextView(Context context, AttributeSet attrs) {
+        return mFactoryStub.createTextView(context, attrs);
+    }
+
+    @Override
+    public AppStyledViewController createAppStyledView(Context activityContext) {
+        AppStyledViewControllerOEMV1 appStyledViewControllerOEMV1 = mOem.createAppStyledView(
+                activityContext);
+        return appStyledViewControllerOEMV1 == null ? new AppStyledViewControllerImpl(
+                activityContext) : new AppStyledViewControllerAdapterV1(
+                appStyledViewControllerOEMV1);
+    }
+
+    private Insets adaptInsets(InsetsOEMV1 insetsOEM) {
+        return new Insets(insetsOEM.getLeft(), insetsOEM.getTop(),
+                insetsOEM.getRight(), insetsOEM.getBottom());
+    }
+
+    @Override
+    public CarUiRecyclerView createRecyclerView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        RecyclerViewAttributesOEMV1 oemAttrs = from(context, attrs);
+        RecyclerViewOEMV1 oemRecyclerView = mOem.createRecyclerView(context, oemAttrs);
+        if (oemRecyclerView != null) {
+            RecyclerViewAdapterV1 rv = new RecyclerViewAdapterV1(context, attrs, 0);
+            rv.setRecyclerViewOEMV1(oemRecyclerView, oemAttrs);
+            return rv;
+        } else {
+            return mFactoryStub.createRecyclerView(context, attrs);
+        }
+    }
+
+    @Override
+    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter(
+            List<? extends CarUiListItem> items) {
+        List<ListItemOEMV1> oemItems = CarUiUtils.convertList(items,
+                PluginFactoryAdapterV2::toOemListItem);
+
+        AdapterOEMV1<? extends ViewHolderOEMV1> oemAdapter = mOem.createListItemAdapter(oemItems);
+
+        if (oemAdapter == null) {
+            return mFactoryStub.createListItemAdapter(items);
+        }
+
+        RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter =
+                new CarUiListItemAdapterAdapterV1(oemAdapter);
+        adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+            @Override
+            public void onChanged() {
+                oemItems.clear();
+                oemItems.addAll(
+                        CarUiUtils.convertList(items, PluginFactoryAdapterV2::toOemListItem));
+            }
+
+            @Override
+            public void onItemRangeChanged(int positionStart, int itemCount) {
+                for (int i = positionStart; i <= positionStart + itemCount; i++) {
+                    oemItems.set(i, toOemListItem(items.get(i)));
+                }
+            }
+
+            @Override
+            public void onItemRangeChanged(int positionStart, int itemCount,
+                    @Nullable Object payload) {
+                for (int i = positionStart; i <= positionStart + itemCount; i++) {
+                    oemItems.set(i, toOemListItem(items.get(i)));
+                }
+            }
+
+            @Override
+            public void onItemRangeInserted(int positionStart, int itemCount) {
+                for (int i = positionStart; i <= positionStart + itemCount; i++) {
+                    oemItems.add(i, toOemListItem(items.get(i)));
+                }
+            }
+
+            @Override
+            public void onItemRangeRemoved(int positionStart, int itemCount) {
+                for (int i = positionStart; i <= positionStart + itemCount; i++) {
+                    oemItems.remove(i);
+                }
+
+            }
+
+            @Override
+            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                for (int i = fromPosition; i <= fromPosition + itemCount; i++) {
+                    ListItemOEMV1 item = oemItems.remove(i);
+                    oemItems.add(toPosition, item);
+                    toPosition++;
+                }
+            }
+        });
+
+        return adapter;
+    }
+
+    private static RecyclerViewAttributesOEMV1 from(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(
+                attrs,
+                R.styleable.CarUiRecyclerView,
+                0,
+                0);
+        final int carUiRecyclerViewLayout = a.getInt(
+                R.styleable.CarUiRecyclerView_layoutStyle,
+                CarUiRecyclerViewLayout.LINEAR);
+        final int spanCount = a.getInt(
+                R.styleable.CarUiRecyclerView_numOfColumns, /* defValue= */ 1);
+        final boolean rotaryScrollEnabled = a.getBoolean(
+                R.styleable.CarUiRecyclerView_rotaryScrollEnabled,
+                /* defValue=*/ false);
+        final int orientation = a.getInt(
+                R.styleable.CarUiRecyclerView_android_orientation,
+                CarUiLayoutStyle.VERTICAL);
+        final boolean reversed = a.getBoolean(
+                R.styleable.CarUiRecyclerView_reverseLayout, false);
+        final int size = a.getInt(R.styleable.CarUiRecyclerView_carUiSize,
+                CarUiRecyclerView.SIZE_LARGE);
+
+        a.recycle();
+
+        int[] attrsArray = new int[]{
+                android.R.attr.layout_width,
+                android.R.attr.layout_height,
+                android.R.attr.minWidth,
+                android.R.attr.minHeight,
+                android.R.attr.paddingStart,
+                android.R.attr.paddingLeft,
+                android.R.attr.paddingEnd,
+                android.R.attr.paddingRight,
+                android.R.attr.paddingTop,
+                android.R.attr.paddingBottom,
+                android.R.attr.layout_marginStart,
+                android.R.attr.layout_marginLeft,
+                android.R.attr.layout_marginEnd,
+                android.R.attr.layout_marginRight,
+                android.R.attr.layout_marginTop,
+                android.R.attr.layout_marginBottom,
+                android.R.attr.background,
+        };
+
+        Arrays.sort(attrsArray);
+        TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray, 0, 0);
+        final int width = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_width),
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        final int height = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_height),
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        final int minWidth = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.minWidth), 0);
+        final int minHeight = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.minHeight), 0);
+        final int paddingLeft = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingLeft), 0);
+        final int paddingRight = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingRight), 0);
+        final int paddingStart = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingStart), 0);
+        final int paddingEnd = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingEnd), 0);
+        final int paddingTop = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingTop), 0);
+        final int paddingBottom = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.paddingBottom), 0);
+        final int marginLeft = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginLeft), 0);
+        final int marginRight = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginRight), 0);
+        final int marginStart = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginStart), 0);
+        final int marginEnd = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginEnd), 0);
+        final int marginTop = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginTop), 0);
+        final int marginBottom = ta.getLayoutDimension(
+                Arrays.binarySearch(attrsArray, android.R.attr.layout_marginBottom), 0);
+        final Drawable background = ta.getDrawable(
+                Arrays.binarySearch(attrsArray, android.R.attr.background));
+        ta.recycle();
+
+        final LayoutStyleOEMV1 layoutStyle = new LayoutStyleOEMV1() {
+            @Override
+            public int getSpanCount() {
+                return spanCount;
+            }
+
+            @Override
+            public int getLayoutType() {
+                switch (carUiRecyclerViewLayout) {
+                    case CarUiRecyclerViewLayout.GRID:
+                        return LayoutStyleOEMV1.LAYOUT_TYPE_GRID;
+                    case CarUiRecyclerViewLayout.LINEAR:
+                    default:
+                        return LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR;
+                }
+            }
+
+            @Override
+            public int getOrientation() {
+                switch (orientation) {
+                    case CarUiLayoutStyle.HORIZONTAL:
+                        return LayoutStyleOEMV1.ORIENTATION_HORIZONTAL;
+                    case CarUiLayoutStyle.VERTICAL:
+                    default:
+                        return LayoutStyleOEMV1.ORIENTATION_VERTICAL;
+                }
+            }
+
+            @Override
+            public boolean getReverseLayout() {
+                return reversed;
+            }
+        };
+
+        boolean isLtr = context.getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_LTR;
+
+        return new RecyclerViewAttributesOEMV1() {
+            @Override
+            public boolean isRotaryScrollEnabled() {
+                return rotaryScrollEnabled;
+            }
+
+            @Override
+            public int getSize() {
+                switch (size) {
+                    case CarUiRecyclerView.SIZE_SMALL:
+                        return RecyclerViewAttributesOEMV1.SIZE_SMALL;
+                    case CarUiRecyclerView.SIZE_MEDIUM:
+                        return RecyclerViewAttributesOEMV1.SIZE_MEDIUM;
+                    case CarUiRecyclerView.SIZE_LARGE:
+                    default:
+                        return RecyclerViewAttributesOEMV1.SIZE_LARGE;
+                }
+            }
+
+            @Override
+            public LayoutStyleOEMV1 getLayoutStyle() {
+                return layoutStyle;
+            }
+
+            @Override
+            public int getLayoutWidth() {
+                return width;
+            }
+
+            @Override
+            public int getLayoutHeight() {
+                return height;
+            }
+
+            @Override
+            public int geMinWidth() {
+                return minWidth;
+            }
+
+            @Override
+            public int getMinHeight() {
+                return minHeight;
+            }
+
+            @Override
+            public int getPaddingLeft() {
+                if (paddingLeft != 0) {
+                    return paddingLeft;
+                } else if (isLtr) {
+                    return paddingStart;
+                } else {
+                    return paddingEnd;
+                }
+            }
+
+            @Override
+            public int getPaddingRight() {
+                if (paddingRight != 0) {
+                    return paddingRight;
+                } else if (isLtr) {
+                    return paddingEnd;
+                } else {
+                    return paddingStart;
+                }
+            }
+
+            @Override
+            public int getPaddingTop() {
+                return paddingTop;
+            }
+
+            @Override
+            public int getPaddingBottom() {
+                return paddingBottom;
+            }
+
+            @Override
+            public int getMarginLeft() {
+                if (marginLeft != 0) {
+                    return marginLeft;
+                } else if (isLtr) {
+                    return marginStart;
+                } else {
+                    return marginEnd;
+                }
+            }
+
+            @Override
+            public int getMarginRight() {
+                if (marginRight != 0) {
+                    return marginRight;
+                } else if (isLtr) {
+                    return marginEnd;
+                } else {
+                    return marginStart;
+                }
+            }
+
+            @Override
+            public int getMarginTop() {
+                return marginTop;
+            }
+
+            @Override
+            public int getMarginBottom() {
+                return marginBottom;
+            }
+
+            @Override
+            public Drawable getBackground() {
+                return background;
+            }
+        };
+    }
+
+    private static ListItemOEMV1 toOemListItem(CarUiListItem item) {
+        if (item instanceof CarUiHeaderListItem) {
+            CarUiHeaderListItem header = (CarUiHeaderListItem) item;
+            return new HeaderListItemOEMV1.Builder(new SpannableString(header.getTitle()))
+                    .setBody(new SpannableString(header.getBody()))
+                    .build();
+        } else if (item instanceof CarUiContentListItem) {
+            CarUiContentListItem contentItem = (CarUiContentListItem) item;
+
+            ContentListItemOEMV1.Builder builder = new ContentListItemOEMV1.Builder(
+                    toOemListItemAction(contentItem.getAction()));
+
+            if (contentItem.getTitle() != null) {
+                builder.setTitle(toOemText(contentItem.getTitle()));
+            }
+
+            if (contentItem.getBody() != null) {
+                builder.setBody(toOemText(contentItem.getBody()));
+            }
+
+            builder.setIcon(contentItem.getIcon(),
+                    toOemListItemIconType(contentItem.getPrimaryIconType()));
+
+            if (contentItem.getAction() == CarUiContentListItem.Action.ICON) {
+                Consumer<ContentListItemOEMV1> listener =
+                        contentItem.getSupplementalIconOnClickListener() != null
+                                ? oemItem ->
+                                contentItem.getSupplementalIconOnClickListener().onClick(
+                                        contentItem) : null;
+                builder.setSupplementalIcon(contentItem.getSupplementalIcon(), listener);
+            }
+
+            if (contentItem.getOnClickListener() != null) {
+                Consumer<ContentListItemOEMV1> listener =
+                        contentItem.getOnClickListener() != null
+                                ? oemItem ->
+                                contentItem.getOnClickListener().onClick(contentItem) : null;
+                builder.setOnItemClickedListener(listener);
+            }
+
+            builder.setOnCheckedChangeListener(oem -> contentItem.setChecked(oem.isChecked()))
+                    .setActionDividerVisible(contentItem.isActionDividerVisible())
+                    .setEnabled(contentItem.isEnabled())
+                    .setChecked(contentItem.isChecked())
+                    .setActivated(contentItem.isActivated())
+                    .setSecure(contentItem.isSecure());
+            return builder.build();
+        } else {
+            throw new IllegalStateException("Unknown view type.");
+        }
+    }
+
+    private static TextOEMV1 toOemText(CarUiText text) {
+        return new TextOEMV1.Builder(text.getTextVariants()).setMaxChars(
+                text.getMaxChars()).setMaxLines(text.getMaxLines()).build();
+    }
+
+    private static List<TextOEMV1> toOemText(List<CarUiText> lines) {
+        List<TextOEMV1> oemLines = new ArrayList<>();
+
+        for (CarUiText line : lines) {
+            oemLines.add(new TextOEMV1.Builder(line.getTextVariants()).setMaxChars(
+                    line.getMaxChars()).setMaxLines(line.getMaxLines()).build());
+        }
+        return oemLines;
+    }
+
+    private static ContentListItemOEMV1.Action toOemListItemAction(
+            CarUiContentListItem.Action action) {
+        switch (action) {
+            case NONE:
+                return ContentListItemOEMV1.Action.NONE;
+            case SWITCH:
+                return ContentListItemOEMV1.Action.SWITCH;
+            case CHECK_BOX:
+                return ContentListItemOEMV1.Action.CHECK_BOX;
+            case RADIO_BUTTON:
+                return ContentListItemOEMV1.Action.RADIO_BUTTON;
+            case ICON:
+                return ContentListItemOEMV1.Action.ICON;
+            case CHEVRON:
+                return ContentListItemOEMV1.Action.CHEVRON;
+            default:
+                throw new IllegalStateException("Unexpected list item action type");
+        }
+    }
+
+    private static ContentListItemOEMV1.IconType toOemListItemIconType(
+            CarUiContentListItem.IconType iconType) {
+        switch (iconType) {
+            case CONTENT:
+                return ContentListItemOEMV1.IconType.CONTENT;
+            case STANDARD:
+                return ContentListItemOEMV1.IconType.STANDARD;
+            case AVATAR:
+                return ContentListItemOEMV1.IconType.AVATAR;
+            default:
+                throw new IllegalStateException("Unexpected list item icon type");
+        }
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactorySingleton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactorySingleton.java
new file mode 100644
index 0000000..206b904
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactorySingleton.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2020 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.ui.pluginsupport;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.os.Build;
+import android.os.Process;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.car.ui.R;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This is a singleton that contains a {@link PluginFactory}. That PluginFactory is used to create
+ * UI components that we want to be customizable by the OEM.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+// TODO: (b/200322953)
+@SuppressLint("LogConditional")
+@RequiresApi(MIN_TARGET_API)
+public final class PluginFactorySingleton {
+    private enum TestingOverride {
+        NOT_SET, ENABLED, DISABLED
+    }
+
+    private static final String TAG = "carui";
+    private static PluginFactory sInstance;
+    private static TestingOverride sTestingOverride = TestingOverride.NOT_SET;
+
+    /**
+     * Only has value during testing, null otherwise
+     */
+    @SuppressLint("StaticFieldLeak")
+    @Nullable
+    private static Context sPluginContext = null;
+
+    /**
+     * Get the {@link PluginFactory}.
+     * <p>
+     * If this is the first time the method is being called, it will initialize it using reflection
+     * to check for the existence of a CarUi plugin, and resolving the appropriate version of the
+     * plugin to use.
+     */
+    public static PluginFactory get(Context context) {
+        try {
+            Trace.beginSection("car-ui-plugin-load");
+            return getImpl(context);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    private static PluginFactory getImpl(Context context) {
+        if (sInstance != null) {
+            return sInstance;
+        }
+
+        context = context.getApplicationContext();
+
+        boolean isPluginEnabled;
+        switch (sTestingOverride) {
+            case ENABLED:
+                isPluginEnabled = true;
+                break;
+            case DISABLED:
+                isPluginEnabled = false;
+                break;
+            case NOT_SET:
+            default:
+                isPluginEnabled = isPluginEnabled(context);
+                break;
+        }
+
+        if (!isPluginEnabled) {
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        // Check if the factory is already on the classpath and if so use it. Note: this would
+        // only happen if the plugin was added as a library to the apk
+        try {
+            Class<?> oemApiUtilClass = Class
+                    .forName("com.android.car.ui.pluginsupport.OemApiUtil");
+            Method getPluginFactoryMethod = oemApiUtilClass.getDeclaredMethod(
+                    "getPluginFactory", Context.class, String.class);
+            getPluginFactoryMethod.setAccessible(true);
+            sInstance = (PluginFactory) getPluginFactoryMethod
+                    .invoke(null, context, context.getPackageName());
+            return sInstance;
+        } catch (ReflectiveOperationException e) {
+            // plugin not found in current classpath
+        }
+
+        String pluginPackageName = getPluginPackageName(context);
+
+        if (TextUtils.isEmpty(pluginPackageName)) {
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        final PackageInfo pluginPackageInfo;
+        try {
+            pluginPackageInfo = context.getPackageManager()
+                    .getPackageInfo(pluginPackageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Could not load CarUi plugin, package "
+                    + pluginPackageName + " was not found.");
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        Context applicationContext = context.getApplicationContext();
+        if (applicationContext instanceof PluginConfigProvider) {
+            Set<PluginSpecifier> deniedPackages =
+                    ((PluginConfigProvider) applicationContext).getPluginDenyList();
+            if (deniedPackages != null && deniedPackages.stream()
+                    .anyMatch(specs -> specs.matches(pluginPackageInfo))) {
+                Log.i(TAG, "Package " + context.getPackageName()
+                        + " denied loading plugin " + pluginPackageName);
+                sInstance = new PluginFactoryStub();
+                return sInstance;
+            }
+        }
+
+        Context pluginContext;
+        try {
+            pluginContext = context.createPackageContext(
+                    pluginPackageName,
+                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Could not load CarUi plugin, package "
+                    + pluginPackageName + " was not found.");
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        AdapterClassLoader adapterClassLoader =
+                instantiateClassLoader(context.getApplicationInfo(),
+                        requireNonNull(PluginFactorySingleton.class.getClassLoader()),
+                        pluginContext.getClassLoader());
+
+        try {
+            Class<?> oemApiUtilClass = adapterClassLoader
+                    .loadClass("com.android.car.ui.pluginsupport.OemApiUtil");
+            Method getPluginFactoryMethod = oemApiUtilClass.getDeclaredMethod(
+                    "getPluginFactory", Context.class, String.class);
+            getPluginFactoryMethod.setAccessible(true);
+            sInstance = (PluginFactory) getPluginFactoryMethod
+                    .invoke(null, pluginContext, context.getPackageName());
+        } catch (ReflectiveOperationException e) {
+            Log.e(TAG, "Could not load CarUi plugin", e);
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        if (sInstance == null) {
+            Log.e(TAG, "Could not load CarUi plugin");
+            sInstance = new PluginFactoryStub();
+            return sInstance;
+        }
+
+        Log.i(TAG, "Loaded plugin " + pluginPackageName
+                + " version " + pluginPackageInfo.getLongVersionCode()
+                + " for package " + context.getPackageName());
+
+        if (sTestingOverride != TestingOverride.NOT_SET) {
+            sPluginContext = pluginContext;
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * This method enables/disables the plugin for testing purposes. It only applies upon the next
+     * call to {@link #get}, components that have already been created won't switch between the
+     * plugin and regular implementations.
+     * <p>
+     * This method is @VisibleForTesting so that unit tests can run both with and without the
+     * plugin. Apps should not use this method. Instead, apps should use {@link
+     * PluginConfigProvider} to control if their plugin is disabled.
+     */
+    @VisibleForTesting
+    public static void setPluginEnabledForTesting(boolean pluginEnabled) {
+        if (pluginEnabled) {
+            sTestingOverride = TestingOverride.ENABLED;
+        } else {
+            sTestingOverride = TestingOverride.DISABLED;
+        }
+        // Cause the next call to get() to reinitialize the plugin
+        sInstance = null;
+        sPluginContext = null;
+    }
+
+    @Nullable
+    @VisibleForTesting
+    public static Context getPluginContext() {
+        return sPluginContext;
+    }
+
+    private PluginFactorySingleton() {
+    }
+
+    @NonNull
+    private static AdapterClassLoader instantiateClassLoader(@NonNull ApplicationInfo appInfo,
+            @NonNull ClassLoader parent, @NonNull ClassLoader sharedlibraryClassLoader) {
+        // All this apk loading code is copied from another Google app
+        List<String> libraryPaths = new ArrayList<>(3);
+        if (appInfo.nativeLibraryDir != null) {
+            libraryPaths.add(appInfo.nativeLibraryDir);
+        }
+        if ((appInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0) {
+            for (String abi : getSupportedAbisForCurrentRuntime()) {
+                libraryPaths.add(appInfo.sourceDir + "!/lib/" + abi);
+            }
+        }
+
+        String flatLibraryPaths = (libraryPaths.size() == 0
+                ? null : TextUtils.join(File.pathSeparator, libraryPaths));
+
+        String apkPaths = appInfo.sourceDir;
+        if (appInfo.sharedLibraryFiles != null && appInfo.sharedLibraryFiles.length > 0) {
+            // Unless you pass PackageManager.GET_SHARED_LIBRARY_FILES this will always be null
+            // HOWEVER, if you running on a device with F5 active, the module's dex files are
+            // always listed in ApplicationInfo.sharedLibraryFiles and should be included in
+            // the classpath.
+            apkPaths +=
+                    File.pathSeparator + TextUtils.join(File.pathSeparator,
+                            appInfo.sharedLibraryFiles);
+        }
+
+        return new AdapterClassLoader(apkPaths, flatLibraryPaths, parent, sharedlibraryClassLoader);
+    }
+
+    private static List<String> getSupportedAbisForCurrentRuntime() {
+        List<String> abis = new ArrayList<>();
+        if (Process.is64Bit()) {
+            Collections.addAll(abis, Build.SUPPORTED_64_BIT_ABIS);
+        } else {
+            Collections.addAll(abis, Build.SUPPORTED_32_BIT_ABIS);
+        }
+        return abis;
+    }
+
+    /**
+     * Return the package name for the Car UI plugin implementation.
+     */
+    @Nullable
+    public static String getPluginPackageName(Context context) {
+        String authority = context.getString(
+                R.string.car_ui_plugin_package_provider_authority_name);
+        ProviderInfo providerInfo = context.getPackageManager().resolveContentProvider(authority,
+                MATCH_ALL | MATCH_DISABLED_COMPONENTS);
+        if (providerInfo == null) {
+            return null;
+        }
+        return providerInfo.packageName;
+    }
+
+    /**
+     * Return if Car UI components should be loaded from the plugin implementation.
+     */
+    public static boolean isPluginEnabled(Context context) {
+        String authority = context.getString(
+                R.string.car_ui_plugin_package_provider_authority_name);
+        PackageManager packageManager = context.getPackageManager();
+        ProviderInfo providerInfo = packageManager.resolveContentProvider(authority,
+                MATCH_ALL | MATCH_DISABLED_COMPONENTS);
+        if (providerInfo == null) {
+            return false;
+        }
+
+        ComponentName componentName = new ComponentName(providerInfo.packageName,
+                providerInfo.name);
+        int state = packageManager.getComponentEnabledSetting(componentName);
+        if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+            return providerInfo.enabled;
+        }
+
+        return state == COMPONENT_ENABLED_STATE_ENABLED;
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryStub.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryStub.java
similarity index 95%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryStub.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryStub.java
index 3bb69b6..677f04b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryStub.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginFactoryStub.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
 import android.app.Activity;
@@ -29,6 +30,7 @@
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.fragment.app.Fragment;
 
 import com.android.car.ui.R;
@@ -48,19 +50,17 @@
 import java.util.List;
 
 /**
- * This is the stub implementation of {@link SharedLibraryFactory}, used when there is no shared
- * library installed on the system. It delegates to the static library implementation of the
+ * This is the stub implementation of {@link PluginFactory}, used when there is no plugin
+ * installed on the system. It delegates to the static library implementation of the
  * necessary components.
  * <p>
  * Do not use from client apps, for car-ui-lib internal use only.
  */
 //TODO(b/179092760) Find a way to prevent apps from using this
-public final class SharedLibraryFactoryStub implements SharedLibraryFactory {
+@RequiresApi(MIN_TARGET_API)
+public final class PluginFactoryStub implements PluginFactory {
 
-    private final Context mContext;
-
-    public SharedLibraryFactoryStub(Context context) {
-        mContext = context;
+    public PluginFactoryStub() {
     }
 
     @Nullable
@@ -138,7 +138,7 @@
      * Activity/Fragments implement {@link InsetsChangedListener}, it will set padding on the
      * content view equal to the insets.
      */
-    public static final class InsetsUpdater {
+    static final class InsetsUpdater {
         // These tags mark views that should overlay the content view in the base layout.
         // OEMs should add them to views in their base layout, ie: android:tag="car_ui_left_inset"
         // Apps will then be able to draw under these views, but will be encouraged to not put
@@ -203,7 +203,7 @@
             mContentViewContainer.addOnLayoutChangeListener(layoutChangeListener);
         }
 
-        public void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
+        void replaceInsetsChangedListenerWith(InsetsChangedListener listener) {
             mInsetsChangedListenerDelegate = listener;
         }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifier.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginSpecifier.java
similarity index 77%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifier.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginSpecifier.java
index 5dc7bec..2998750 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibrarySpecifier.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginSpecifier.java
@@ -13,27 +13,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.pm.PackageInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 /**
- * This is a data class that represents a particular shared library. It can be used
- * via {@link SharedLibraryConfigProvider} to deny loading certain shared libraries.
+ * This is a data class that represents a particular plugin. It can be used
+ * via {@link PluginConfigProvider} to deny loading certain plugins.
  *
  * Create a new instance of this class using {@link #builder()}.
  */
-public final class SharedLibrarySpecifier {
+@TargetApi(MIN_TARGET_API)
+public final class PluginSpecifier {
 
     @Nullable
     private final String mPackageName;
     @Nullable
     private final Long mMaxVersion;
 
-    private SharedLibrarySpecifier(Builder builder) {
+    private PluginSpecifier(Builder builder) {
         mPackageName = builder.mPackageName;
         mMaxVersion = builder.mMaxVersion;
     }
@@ -51,7 +55,7 @@
         return nameMatches && versionMatches;
     }
 
-    /** A builder class for {@link SharedLibrarySpecifier} */
+    /** A builder class for {@link PluginSpecifier} */
     public static class Builder {
         @Nullable
         private String mPackageName;
@@ -72,7 +76,7 @@
 
         /**
          * Sets the maximum version to match. This is the {@code android:versionCode} integer
-         * in the shared library's {@code <manifest>} tag. If it is unset, any version will
+         * in the plugin's {@code <manifest>} tag. If it is unset, any version will
          * be matched.
          *
          * @return This builder, for chaining calls.
@@ -82,9 +86,9 @@
             return this;
         }
 
-        /** Builds the {@link SharedLibrarySpecifier} */
-        public SharedLibrarySpecifier build() {
-            return new SharedLibrarySpecifier(this);
+        /** Builds the {@link PluginSpecifier} */
+        public PluginSpecifier build() {
+            return new PluginSpecifier(this);
         }
     };
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProvider.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProvider.java
similarity index 62%
rename from car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProvider.java
rename to car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProvider.java
index daf2bda..ac2479e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProvider.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProvider.java
@@ -13,40 +13,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrarysupport;
+package com.android.car.ui.pluginsupport;
 
 import android.content.Context;
 
 /**
  * An interface for objects that support providing a list a supported versions of
- * {@link SharedLibraryFactory} to the app. See
- * {@link #getSharedLibraryFactory(int, Context, String)}} for more information.
+ * {@link PluginFactory} to the app. See
+ * {@link #getPluginFactory(int, Context, String)}} for more information.
  *
  * Do not use from client apps, for car-ui-lib internal use only.
  */
 //TODO(b/179092760) Find a way to prevent apps from using this
-public interface SharedLibraryVersionProvider {
+public interface PluginVersionProvider {
     /**
-     * Returns an object that implements {@link SharedLibraryFactory} or a later version.
+     * Returns an object that implements {@link PluginFactory} or a later version.
      *
      * OEMs should aim to return the highest version of the factory possible that is <=
-     * {@code maxVersion}. If the shared library is not able to provide that version,
+     * {@code maxVersion}. If the plugin is not able to provide that version,
      * it may return null, in which case car-ui-lib will fall back to it's static,
      * uncustomized implementation.
      *
-     * The shared library may also choose to return different SharedLibraryFactories based on
+     * The plugin may also choose to return different PluginFactories based on
      * certain conditions, like what type of device this is, or what app it's being used in.
      * (The app can be discovered via {@link android.app.Application#getProcessName()}
      *
-     * @param maxVersion The maximum version of {@link SharedLibraryFactory} supported by the
+     * @param maxVersion The maximum version of {@link PluginFactory} supported by the
      *                   app.
-     * @param context The shared library's context. It uses the shared library's classloader,
-     *                so layout inflaters created from it can use views defined in the shared lib.
-     * @param packageName The package name of the app creating the shared library. Can be used
+     * @param context The plugin's context. It uses the plugin's classloader,
+     *                so layout inflaters created from it can use views defined in the plugin.
+     * @param packageName The package name of the app creating the plugin. Can be used
      *                    to provide per-app customizations.
      *
-     * @return An object implementing {@link SharedLibraryFactory} for a version <=
+     * @return An object implementing {@link PluginFactory} for a version <=
      *         {@code maxVersion}.
      */
-    Object getSharedLibraryFactory(int maxVersion, Context context, String packageName);
+    Object getPluginFactory(int maxVersion, Context context, String packageName);
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProviderAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProviderAdapterV1.java
new file mode 100644
index 0000000..a4fa714
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/pluginsupport/PluginVersionProviderAdapterV1.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.ui.pluginsupport;
+
+import android.content.Context;
+
+import com.android.car.ui.plugin.oemapis.PluginVersionProviderOEMV1;
+
+/**
+ * This class is an wrapper around {@link PluginVersionProviderOEMV1} that implements
+ * {@link PluginVersionProvider}, to provide a version-agnostic way of interfacing with
+ * the OEM's PluginVersionProvider.
+ */
+final class PluginVersionProviderAdapterV1 implements PluginVersionProvider {
+
+    private PluginVersionProviderOEMV1 mOemProvider;
+
+    PluginVersionProviderAdapterV1(
+            PluginVersionProviderOEMV1 oemVersionProvider) {
+        mOemProvider = oemVersionProvider;
+    }
+
+    @Override
+    public Object getPluginFactory(int maxVersion, Context context, String packageName) {
+        return mOemProvider.getPluginFactory(maxVersion, context, packageName);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java
index 5929e02..c3af2ec 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDialogFragment.java
@@ -230,8 +230,8 @@
     /**
      * Called when the dialog is dismissed.
      *
-     * @param positiveResult {@code true} if the dialog was dismissed with {@link
-     *                       DialogInterface#BUTTON_POSITIVE}.
+     * @param positiveResult {@code true} if the dialog was dismissed with
+     * {@code DialogInterface#BUTTON_POSITIVE}.
      */
     protected abstract void onDialogClosed(boolean positiveResult);
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java
index eacd1f3..7526f9f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiDropDownPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 
@@ -34,6 +37,7 @@
  * the preference.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiDropDownPreference extends DropDownPreference
         implements UxRestrictablePreference {
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java
index 81ee09b..0257973 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiEditTextPreference.java
@@ -16,8 +16,10 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.getAttr;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -39,6 +41,7 @@
  * the preference.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiEditTextPreference extends EditTextPreference
         implements UxRestrictablePreference {
 
@@ -72,7 +75,7 @@
                 defStyleAttr,
                 defStyleRes);
 
-        mShowChevron = a.getBoolean(R.styleable.CarUiPreference_showChevron, true);
+        mShowChevron = a.getBoolean(R.styleable.CarUiPreference_carUiShowChevron, true);
         mUxRestricted = a.getBoolean(R.styleable.CarUiPreference_car_ui_ux_restricted, false);
 
         a.recycle();
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java
index 385cbf2..01989c8 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiListPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -35,6 +38,7 @@
  * the preference.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiListPreference extends ListPreference implements UxRestrictablePreference {
 
     private Consumer<Preference> mRestrictedClickListener;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
index 8649377..c874675 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiMultiSelectListPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -35,6 +38,7 @@
  * to the preference.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiMultiSelectListPreference extends MultiSelectListPreference
         implements UxRestrictablePreference {
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
index 34abb54..b4a6919 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -35,11 +38,15 @@
  * the preference if there is one of fragment, intent or onPreferenceClickListener set.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public class CarUiPreference extends Preference implements DisabledPreferenceCallback {
+@TargetApi(MIN_TARGET_API)
+public class CarUiPreference extends Preference implements DisabledPreferenceCallback,
+        ClickableWhileDisabledPreference {
     private boolean mShowChevron;
 
     private Consumer<Preference> mRestrictedClickListener;
+    private Consumer<Preference> mDisabledClickListener;
     private boolean mUxRestricted = false;
+    private boolean mIsClickableWhileDisabled = false;
 
     public CarUiPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
@@ -66,8 +73,13 @@
                 defStyleAttr,
                 defStyleRes);
 
-        mShowChevron = a.getBoolean(R.styleable.CarUiPreference_showChevron, true);
+        mShowChevron = a.getBoolean(R.styleable.CarUiPreference_carUiShowChevron, true);
         mUxRestricted = a.getBoolean(R.styleable.CarUiPreference_car_ui_ux_restricted, false);
+        mIsClickableWhileDisabled = a.getBoolean(
+                R.styleable.CarUiPreference_carUiClickableWhileDisabled, false);
+        if (mIsClickableWhileDisabled) {
+            super.setShouldDisableView(false);
+        }
 
         a.recycle();
     }
@@ -76,7 +88,8 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
-        CarUiUtils.makeAllViewsUxRestricted(holder.itemView, isUxRestricted());
+        CarUiUtils.makeAllViewsEnabledAndUxRestricted(holder.itemView, isEnabled(),
+                isUxRestricted());
     }
 
     @Override
@@ -109,7 +122,11 @@
     @Override
     @SuppressWarnings("RestrictTo")
     public void performClick() {
-        if ((isEnabled() || isSelectable()) && isUxRestricted()) {
+        if (!isEnabled() && mIsClickableWhileDisabled) {
+            if (mDisabledClickListener != null) {
+                mDisabledClickListener.accept(this);
+            }
+        } else if ((isEnabled() || isSelectable()) && isUxRestricted()) {
             if (mRestrictedClickListener != null) {
                 mRestrictedClickListener.accept(this);
             }
@@ -118,6 +135,12 @@
         }
     }
 
+    @Override
+    public void setShouldDisableView(boolean shouldDisableView) {
+        throw new UnsupportedOperationException("android:shouldDisableView is"
+                + "unsupported on CarUiPreferences");
+    }
+
     public void setShowChevron(boolean showChevron) {
         mShowChevron = showChevron;
     }
@@ -143,4 +166,28 @@
         mUxRestricted = restricted;
         notifyChanged();
     }
+
+    @Override
+    public void setClickableWhileDisabled(boolean clickableWhileDisabled) {
+        if (mIsClickableWhileDisabled != clickableWhileDisabled) {
+            super.setShouldDisableView(!clickableWhileDisabled);
+            mIsClickableWhileDisabled = clickableWhileDisabled;
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public boolean isClickableWhileDisabled() {
+        return mIsClickableWhileDisabled;
+    }
+
+    @Override
+    public void setDisabledClickListener(Consumer<Preference> listener) {
+        mDisabledClickListener = listener;
+    }
+
+    @Override
+    public Consumer<Preference> getDisabledClickListener() {
+        return mDisabledClickListener;
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java
index 432967c..cc13144 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiRadioButtonPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.RadioButton;
@@ -32,6 +35,7 @@
 
 /** A preference which shows a radio button at the start of the preference. */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiRadioButtonPreference extends TwoStatePreference
         implements UxRestrictablePreference {
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
index 74e7f2f..c52e701 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSeekBarDialogPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -35,6 +38,7 @@
 
 /** A class implements some basic methods of a seekbar dialog preference. */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiSeekBarDialogPreference extends DialogPreference
         implements DialogFragmentCallbacks, UxRestrictablePreference {
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java
index f2f1839..324fa62 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiSwitchPreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -32,13 +35,17 @@
 
 /**
  * This class is the same as the base {@link SwitchPreference} class, except it implements
- * {@link UxRestrictablePreference}
+ * {@link UxRestrictablePreference} and {@link ClickableWhileDisabledPreference}
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public class CarUiSwitchPreference extends SwitchPreference implements DisabledPreferenceCallback {
+@TargetApi(MIN_TARGET_API)
+public class CarUiSwitchPreference extends SwitchPreference implements DisabledPreferenceCallback,
+        ClickableWhileDisabledPreference {
 
     private Consumer<Preference> mRestrictedClickListener;
+    private Consumer<Preference> mDisabledClickListener;
     private boolean mUxRestricted = false;
+    private boolean mIsClickableWhileDisabled = false;
 
     public CarUiSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
@@ -64,6 +71,11 @@
     private void init(AttributeSet attrs) {
         TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CarUiPreference);
         mUxRestricted = a.getBoolean(R.styleable.CarUiPreference_car_ui_ux_restricted, false);
+        mIsClickableWhileDisabled = a.getBoolean(
+                R.styleable.CarUiPreference_carUiClickableWhileDisabled, false);
+        if (mIsClickableWhileDisabled) {
+            super.setShouldDisableView(false);
+        }
         a.recycle();
     }
 
@@ -71,13 +83,18 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
-        CarUiUtils.makeAllViewsUxRestricted(holder.itemView, isUxRestricted());
+        CarUiUtils.makeAllViewsEnabledAndUxRestricted(holder.itemView, isEnabled(),
+                isUxRestricted());
     }
 
     @Override
     @SuppressWarnings("RestrictTo")
     public void performClick() {
-        if ((isEnabled() || isSelectable()) && isUxRestricted()) {
+        if (!isEnabled() && mIsClickableWhileDisabled) {
+            if (mDisabledClickListener != null) {
+                mDisabledClickListener.accept(this);
+            }
+        } else if ((isEnabled() || isSelectable()) && isUxRestricted()) {
             if (mRestrictedClickListener != null) {
                 mRestrictedClickListener.accept(this);
             }
@@ -87,6 +104,12 @@
     }
 
     @Override
+    public void setShouldDisableView(boolean shouldDisableView) {
+        throw new UnsupportedOperationException("Dynamically setting shouldDisableView is"
+                + "unsupported in CarUiPreferences");
+    }
+
+    @Override
     public void setUxRestricted(boolean restricted) {
         if (mUxRestricted != restricted) {
             mUxRestricted = restricted;
@@ -109,4 +132,28 @@
     public Consumer<Preference> getOnClickWhileRestrictedListener() {
         return mRestrictedClickListener;
     }
+
+    @Override
+    public void setClickableWhileDisabled(boolean clickableWhileDisabled) {
+        if (mIsClickableWhileDisabled != clickableWhileDisabled) {
+            super.setShouldDisableView(!clickableWhileDisabled);
+            mIsClickableWhileDisabled = clickableWhileDisabled;
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public boolean isClickableWhileDisabled() {
+        return mIsClickableWhileDisabled;
+    }
+
+    @Override
+    public void setDisabledClickListener(Consumer<Preference> listener) {
+        mDisabledClickListener = listener;
+    }
+
+    @Override
+    public Consumer<Preference> getDisabledClickListener() {
+        return mDisabledClickListener;
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionBasePreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionBasePreference.java
index 5151642..bfc3d83 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionBasePreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionBasePreference.java
@@ -16,6 +16,9 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
@@ -25,13 +28,18 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StyleableRes;
+import androidx.preference.Preference;
 
 import com.android.car.ui.R;
 
+import java.util.function.Consumer;
+
 /**
  * A base class for several types of preferences, that all have a main click action along
  * with a secondary action.
  */
+@SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public abstract class CarUiTwoActionBasePreference extends CarUiPreference {
 
     protected boolean mSecondaryActionEnabled = true;
@@ -67,7 +75,7 @@
                 .obtainStyledAttributes(attrs, R.styleable.CarUiTwoActionBasePreference);
         try {
             disallowResourceIds(a,
-                    R.styleable.CarUiTwoActionBasePreference_layout,
+                    R.styleable.CarUiTwoActionBasePreference_carUiLayout,
                     R.styleable.CarUiTwoActionBasePreference_android_layout,
                     R.styleable.CarUiTwoActionBasePreference_widgetLayout,
                     R.styleable.CarUiTwoActionBasePreference_android_widgetLayout);
@@ -123,8 +131,19 @@
      * Like {@link #onClick()}, but for the secondary action.
      */
     public void performSecondaryActionClick() {
-        if (mSecondaryActionEnabled && mSecondaryActionVisible) {
-            performSecondaryActionClickInternal();
+        if (isSecondaryActionEnabled()) {
+            if (isUxRestricted()) {
+                Consumer<Preference> restrictedListener = getOnClickWhileRestrictedListener();
+                if (restrictedListener != null) {
+                    restrictedListener.accept(this);
+                }
+            } else {
+                performSecondaryActionClickInternal();
+            }
+        } else if (isClickableWhileDisabled()) {
+            if (getDisabledClickListener() != null) {
+                getDisabledClickListener().accept(this);
+            }
         }
     }
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionIconPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionIconPreference.java
index 1ed5d45..45f35e4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionIconPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionIconPreference.java
@@ -29,12 +29,10 @@
 import androidx.annotation.DrawableRes;
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.car.ui.R;
-
-import java.util.function.Consumer;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * A preference that has an icon button that can be pressed independently of pressing the main
@@ -83,15 +81,8 @@
 
     @Override
     protected void performSecondaryActionClickInternal() {
-        if (isSecondaryActionEnabled()) {
-            if (isUxRestricted()) {
-                Consumer<Preference> restrictedListener = getOnClickWhileRestrictedListener();
-                if (restrictedListener != null) {
-                    restrictedListener.accept(this);
-                }
-            } else if (mSecondaryActionOnClickListener != null) {
-                mSecondaryActionOnClickListener.run();
-            }
+        if (mSecondaryActionOnClickListener != null) {
+            mSecondaryActionOnClickListener.run();
         }
     }
 
@@ -111,15 +102,18 @@
         holder.itemView.setFocusable(false);
         holder.itemView.setClickable(false);
         firstActionContainer.setOnClickListener(this::performClickUnrestricted);
-        firstActionContainer.setEnabled(isEnabled());
-        firstActionContainer.setFocusable(isEnabled());
+        firstActionContainer.setEnabled(isEnabled() || isClickableWhileDisabled());
+        firstActionContainer.setFocusable(isEnabled() || isClickableWhileDisabled());
 
         secondActionContainer.setVisibility(mSecondaryActionVisible ? View.VISIBLE : View.GONE);
         iconView.setImageDrawable(mSecondaryActionIcon);
-        iconView.setEnabled(isSecondaryActionEnabled());
-        secondaryButton.setEnabled(isSecondaryActionEnabled());
-        secondaryButton.setFocusable(isSecondaryActionEnabled());
-        secondaryButton.setOnClickListener(v -> performSecondaryActionClickInternal());
+        iconView.setEnabled(isSecondaryActionEnabled() || isClickableWhileDisabled());
+        secondaryButton.setEnabled(isSecondaryActionEnabled() || isClickableWhileDisabled());
+        secondaryButton.setFocusable(isSecondaryActionEnabled() || isClickableWhileDisabled());
+        secondaryButton.setOnClickListener(v -> performSecondaryActionClick());
+
+        CarUiUtils.makeAllViewsEnabledAndUxRestricted(secondaryButton, isSecondaryActionEnabled(),
+                isUxRestricted());
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionSwitchPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionSwitchPreference.java
index 1ab02bb..940619a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionSwitchPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionSwitchPreference.java
@@ -16,18 +16,20 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.Switch;
 
 import androidx.annotation.Nullable;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
 
 import java.util.function.Consumer;
 
@@ -36,6 +38,7 @@
  * body of the preference.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class CarUiTwoActionSwitchPreference extends CarUiTwoActionBasePreference {
     @Nullable
     protected Consumer<Boolean> mSecondaryActionOnClickListener;
@@ -68,19 +71,10 @@
 
     @Override
     protected void performSecondaryActionClickInternal() {
-        if (isSecondaryActionEnabled()) {
-            if (isUxRestricted()) {
-                Consumer<Preference> restrictedListener = getOnClickWhileRestrictedListener();
-                if (restrictedListener != null) {
-                    restrictedListener.accept(this);
-                }
-            } else {
-                mSecondaryActionChecked = !mSecondaryActionChecked;
-                notifyChanged();
-                if (mSecondaryActionOnClickListener != null) {
-                    mSecondaryActionOnClickListener.accept(mSecondaryActionChecked);
-                }
-            }
+        mSecondaryActionChecked = !mSecondaryActionChecked;
+        notifyChanged();
+        if (mSecondaryActionOnClickListener != null) {
+            mSecondaryActionOnClickListener.accept(mSecondaryActionChecked);
         }
     }
 
@@ -100,16 +94,23 @@
         holder.itemView.setFocusable(false);
         holder.itemView.setClickable(false);
         firstActionContainer.setOnClickListener(this::performClickUnrestricted);
-        firstActionContainer.setEnabled(isEnabled() || isUxRestricted());
-        firstActionContainer.setFocusable(isEnabled() || isUxRestricted());
+        firstActionContainer.setEnabled(
+                isEnabled() || isUxRestricted() || isClickableWhileDisabled());
+        firstActionContainer.setFocusable(
+                isEnabled() || isUxRestricted() || isClickableWhileDisabled());
 
         secondActionContainer.setVisibility(mSecondaryActionVisible ? View.VISIBLE : View.GONE);
         s.setChecked(mSecondaryActionChecked);
         s.setEnabled(isSecondaryActionEnabled());
 
-        secondaryAction.setOnClickListener(v -> performSecondaryActionClickInternal());
-        secondaryAction.setEnabled(isSecondaryActionEnabled() || isUxRestricted());
-        secondaryAction.setFocusable(isSecondaryActionEnabled() || isUxRestricted());
+        secondaryAction.setOnClickListener(v -> performSecondaryActionClick());
+        secondaryAction.setEnabled(
+                isSecondaryActionEnabled() || isUxRestricted() || isClickableWhileDisabled());
+        secondaryAction.setFocusable(
+                isSecondaryActionEnabled() || isUxRestricted() || isClickableWhileDisabled());
+
+        CarUiUtils.makeAllViewsEnabledAndUxRestricted(secondaryAction, isSecondaryActionEnabled(),
+                isUxRestricted());
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionTextPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionTextPreference.java
index a28baf2..d47a73d 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionTextPreference.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/CarUiTwoActionTextPreference.java
@@ -26,12 +26,10 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
-import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.car.ui.R;
-
-import java.util.function.Consumer;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * A preference that has a text button that can be pressed independently of pressing the main
@@ -86,15 +84,8 @@
 
     @Override
     protected void performSecondaryActionClickInternal() {
-        if (isSecondaryActionEnabled()) {
-            if (isUxRestricted()) {
-                Consumer<Preference> restrictedListener = getOnClickWhileRestrictedListener();
-                if (restrictedListener != null) {
-                    restrictedListener.accept(this);
-                }
-            } else if (mSecondaryActionOnClickListener != null) {
-                mSecondaryActionOnClickListener.run();
-            }
+        if (mSecondaryActionOnClickListener != null) {
+            mSecondaryActionOnClickListener.run();
         }
     }
 
@@ -112,14 +103,17 @@
         holder.itemView.setFocusable(false);
         holder.itemView.setClickable(false);
         firstActionContainer.setOnClickListener(this::performClickUnrestricted);
-        firstActionContainer.setEnabled(isEnabled());
-        firstActionContainer.setFocusable(isEnabled());
+        firstActionContainer.setEnabled(isEnabled() || isClickableWhileDisabled());
+        firstActionContainer.setFocusable(isEnabled() || isClickableWhileDisabled());
 
         secondActionContainer.setVisibility(mSecondaryActionVisible ? View.VISIBLE : View.GONE);
         secondaryButton.setText(mSecondaryActionText);
-        secondaryButton.setOnClickListener(v -> performSecondaryActionClickInternal());
-        secondaryButton.setEnabled(isSecondaryActionEnabled());
-        secondaryButton.setFocusable(isSecondaryActionEnabled());
+        secondaryButton.setOnClickListener(v -> performSecondaryActionClick());
+        secondaryButton.setEnabled(isSecondaryActionEnabled() || isClickableWhileDisabled());
+        secondaryButton.setFocusable(isSecondaryActionEnabled() || isClickableWhileDisabled());
+
+        CarUiUtils.makeAllViewsEnabledAndUxRestricted(secondaryButton, isSecondaryActionEnabled(),
+                isUxRestricted());
     }
 
     @Nullable
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ClickableWhileDisabledPreference.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ClickableWhileDisabledPreference.java
new file mode 100644
index 0000000..dbe149b
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ClickableWhileDisabledPreference.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.ui.preference;
+
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import java.util.function.Consumer;
+
+/**
+ * An interface for preferences that can be clicked while disabled.
+ *
+ * These preference will appear disabled but will still perform an action when clicked.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public interface ClickableWhileDisabledPreference {
+    /** Sets this preference as clickable while disabled or not */
+    void setClickableWhileDisabled(boolean clickableWhileDisabled);
+
+    /** Returns if this preference is currently clickable while disabled. */
+    boolean isClickableWhileDisabled();
+
+    /** Sets a listener to be called if the preference is clicked while it is disabled */
+    void setDisabledClickListener(@Nullable Consumer<Preference> listener);
+
+    /** Gets the listener to be called if the preference is clicked while it is disabled */
+    @Nullable
+    Consumer<Preference> getDisabledClickListener();
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
index b2407a2..94a97ea 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/ListPreferenceFragment.java
@@ -16,8 +16,10 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.preference.PreferenceDialogFragment.ARG_KEY;
 
+import android.annotation.TargetApi;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -29,6 +31,7 @@
 import androidx.preference.DialogPreference;
 import androidx.preference.ListPreference;
 import androidx.preference.Preference;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.FocusArea;
 import com.android.car.ui.R;
@@ -51,6 +54,7 @@
  * A fragment that provides a layout with a list of options associated with a {@link
  * ListPreference}.
  */
+@TargetApi(MIN_TARGET_API)
 public class ListPreferenceFragment extends Fragment implements InsetsChangedListener {
 
     private ListPreference mPreference;
@@ -94,7 +98,7 @@
         mPreference = getListPreference();
         if (toolbar != null) {
             toolbar.setTitle(mPreference.getTitle());
-            toolbar.setSubtitle(null);
+            toolbar.setSubtitle("");
             if (toolbar.isStateSet()) {
                 toolbar.setState(Toolbar.State.SUBPAGE);
             } else {
@@ -155,6 +159,15 @@
         }
 
         carUiRecyclerView.setAdapter(adapter);
+        carUiRecyclerView.scrollToPosition(mSelectedIndex);
+        carUiRecyclerView.post(
+                () -> {
+                    RecyclerView.ViewHolder viewHolder =
+                            carUiRecyclerView.findViewHolderForAdapterPosition(mSelectedIndex);
+                    if (viewHolder != null) {
+                        viewHolder.itemView.requestFocus();
+                    }
+                });
     }
 
     @Override
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
index b876c23..a51cef7 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/MultiSelectListPreferenceFragment.java
@@ -16,8 +16,10 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.preference.PreferenceDialogFragment.ARG_KEY;
 
+import android.annotation.TargetApi;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -52,6 +54,7 @@
  * A fragment that provides a layout with a list of options associated with a {@link
  * CarUiMultiSelectListPreference}.
  */
+@TargetApi(MIN_TARGET_API)
 public class MultiSelectListPreferenceFragment extends Fragment implements InsetsChangedListener {
 
     private CarUiMultiSelectListPreference mPreference;
@@ -95,7 +98,7 @@
         recyclerView.setClipToPadding(false);
         if (toolbar != null) {
             toolbar.setTitle(mPreference.getTitle());
-            toolbar.setSubtitle(null);
+            toolbar.setSubtitle("");
             if (toolbar.isStateSet()) {
                 toolbar.setState(Toolbar.State.SUBPAGE);
             } else {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
index 5fb8c5a..58c0841 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/preference/PreferenceFragment.java
@@ -16,15 +16,20 @@
 
 package com.android.car.ui.preference;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
+
 import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
 import android.util.Pair;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
 import androidx.preference.DialogPreference;
@@ -38,12 +43,14 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 import androidx.preference.TwoStatePreference;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.FocusArea;
 import com.android.car.ui.R;
 import com.android.car.ui.baselayout.Insets;
 import com.android.car.ui.baselayout.InsetsChangedListener;
 import com.android.car.ui.core.CarUi;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
 import com.android.car.ui.toolbar.Toolbar;
 import com.android.car.ui.toolbar.ToolbarController;
 import com.android.car.ui.utils.CarUiUtils;
@@ -65,13 +72,34 @@
  * defaultValue, and enabled state.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@RequiresApi(MIN_TARGET_API)
 public abstract class PreferenceFragment extends PreferenceFragmentCompat implements
         InsetsChangedListener {
 
+    /**
+     * Only for PreferenceFragment internal usage. Apps shouldn't use this as the
+     * {@link RecyclerView} that's provided here is not the real RecyclerView and has very limited
+     * functionality.
+     */
+    public interface AndroidxRecyclerViewProvider {
+
+        /**
+         * returns instance of {@link RecyclerView} that proxies PreferenceFragment calls to the
+         * real RecyclerView implementation.
+         */
+        RecyclerView getRecyclerView();
+    }
+
     private static final String TAG = "CarUiPreferenceFragment";
     private static final String DIALOG_FRAGMENT_TAG =
             "com.android.car.ui.PreferenceFragment.DIALOG";
 
+    @NonNull
+    private CarUiRecyclerView mCarUiRecyclerView;
+    @Nullable
+    private String mLastSelectedPrefKey;
+    private int mLastFocusedAndSelectedPrefPosition;
+
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
@@ -98,7 +126,7 @@
         if (preferenceScreen != null) {
             toolbar.setTitle(preferenceScreen.getTitle());
         } else {
-            toolbar.setTitle(null);
+            toolbar.setTitle("");
         }
     }
 
@@ -145,11 +173,10 @@
     @Override
     public void onCarUiInsetsChanged(@NonNull Insets insets) {
         View view = requireView();
-        FocusArea focusArea = CarUiUtils.requireViewByRefId(view, R.id.car_ui_focus_area);
+        FocusArea focusArea = requireViewByRefId(view, R.id.car_ui_focus_area);
         focusArea.setHighlightPadding(0, insets.getTop(), 0, insets.getBottom());
         focusArea.setBoundsOffset(0, insets.getTop(), 0, insets.getBottom());
-        CarUiUtils.requireViewByRefId(view, R.id.recycler_view)
-                .setPadding(0, insets.getTop(), 0, insets.getBottom());
+        getCarUiRecyclerView().setPadding(0, insets.getTop(), 0, insets.getBottom());
         view.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
     }
 
@@ -210,7 +237,7 @@
             }
 
             Context context = getContext();
-            getActivity().getSupportFragmentManager().beginTransaction()
+            getParentFragmentManager().beginTransaction()
                     .setCustomAnimations(
                             CarUiUtils.getAttrResourceId(context,
                                     android.R.attr.fragmentOpenEnterAnimation),
@@ -226,6 +253,23 @@
         }
     }
 
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mLastSelectedPrefKey != null) {
+            scrollToPreference(mLastSelectedPrefKey);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(Preference preference) {
+        mLastSelectedPrefKey = preference.getKey();
+        View focus = getView().findFocus();
+        mLastFocusedAndSelectedPrefPosition = mCarUiRecyclerView.getChildLayoutPosition(focus);
+
+        return super.onPreferenceTreeClick(preference);
+    }
+
     /**
      * This override of setPreferenceScreen replaces preferences with their CarUi versions first.
      */
@@ -274,6 +318,22 @@
         }
     }
 
+    /**
+     * In order to change the layout for {@link PreferenceFragment}, make sure the correct layout is
+     * passed to PreferenceFragment.CarUi theme.
+     * Override ht method in order to inflate {@link CarUiRecyclerView}
+     */
+    @NonNull
+    public CarUiRecyclerView onCreateCarUiRecyclerView(LayoutInflater inflater, ViewGroup parent,
+                                                       Bundle savedInstanceState) {
+        return requireViewByRefId(parent, R.id.recycler_view);
+    }
+
+    @NonNull
+    public CarUiRecyclerView getCarUiRecyclerView() {
+        return mCarUiRecyclerView;
+    }
+
     // Mapping from regular preferences to CarUi preferences.
     // Order is important, subclasses must come before their base classes
     // Make sure all the following classes are added to proguard configuration.
@@ -325,6 +385,37 @@
         return preference;
     }
 
+    @Override
+    public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
+            Bundle savedInstanceState) {
+        mCarUiRecyclerView = onCreateCarUiRecyclerView(inflater, parent, savedInstanceState);
+        RecyclerView recyclerView = null;
+        if (mCarUiRecyclerView instanceof AndroidxRecyclerViewProvider) {
+            recyclerView = ((AndroidxRecyclerViewProvider) mCarUiRecyclerView).getRecyclerView();
+        }
+        if (recyclerView == null) {
+            recyclerView = super.onCreateRecyclerView(inflater, parent, savedInstanceState);
+        }
+
+        // When not in touch mode, focus on the previously focused and selected item, if any.
+        if (mCarUiRecyclerView != null) {
+            mCarUiRecyclerView.addOnChildAttachStateChangeListener(
+                        new RecyclerView.OnChildAttachStateChangeListener() {
+                            @Override
+                            public void onChildViewAttachedToWindow(View view) {
+                                int position = mCarUiRecyclerView.getChildLayoutPosition(view);
+                                if (position == mLastFocusedAndSelectedPrefPosition) {
+                                    view.requestFocus();
+                                }
+                            }
+                            @Override
+                            public void onChildViewDetachedFromWindow(View view) {
+                            }
+                });
+        }
+        return recyclerView;
+    }
+
     /**
      * Copies all the properties of one preference to another.
      *
@@ -346,12 +437,15 @@
         to.setIconSpaceReserved(from.isIconSpaceReserved());
         to.setWidgetLayoutResource(from.getWidgetLayoutResource());
         to.setPreferenceDataStore(from.getPreferenceDataStore());
-        to.setShouldDisableView(from.getShouldDisableView());
         to.setSingleLineTitle(from.isSingleLineTitle());
         to.setVisible(from.isVisible());
         to.setLayoutResource(from.getLayoutResource());
         to.setCopyingEnabled(from.isCopyingEnabled());
 
+        if (!(to instanceof UxRestrictablePreference)) {
+            to.setShouldDisableView(from.getShouldDisableView());
+        }
+
         if (from.getSummaryProvider() != null) {
             to.setSummaryProvider(from.getSummaryProvider());
         } else {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
index 3eee3de..42bb62a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiContentListItem.java
@@ -127,6 +127,7 @@
     private OnCheckedChangeListener mOnCheckedChangeListener;
     @Nullable
     private OnClickListener mSupplementalIconOnClickListener;
+    private boolean mIsSecure;
 
     public CarUiContentListItem(@NonNull Action action) {
         mAction = action;
@@ -404,4 +405,22 @@
     public OnCheckedChangeListener getOnCheckedChangeListener() {
         return mOnCheckedChangeListener;
     }
+
+    /**
+     * Sets if the list item is secure or not. If it is secure, it won't sent any
+     * click events if there is a full or partial overlay on the screen when
+     * they're clicked.
+     *
+     * @param secure If the list item is secure or not.
+     */
+    public void setSecure(boolean secure) {
+        mIsSecure = secure;
+    }
+
+    /**
+     * Returns if this is a security-sensitive list item or not. See {@link #setSecure}.
+     */
+    public boolean isSecure() {
+        return mIsSecure;
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiGridLayoutStyle.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiGridLayoutStyle.java
index e3caeda..2dfca0b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiGridLayoutStyle.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiGridLayoutStyle.java
@@ -23,6 +23,7 @@
 import androidx.recyclerview.widget.RecyclerView.LayoutManager;
 
 import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout;
+import com.android.car.ui.recyclerview.CarUiRecyclerView.Size;
 
 /**
  * CarUi proxy class for {@link GridLayoutManager}
@@ -30,13 +31,12 @@
 public final class CarUiGridLayoutStyle implements CarUiLayoutStyle {
 
     private int mSpanCount = 1;
-    @CarUiRecyclerViewLayout
-    private  int mLayoutType = CarUiRecyclerViewLayout.GRID;
     @Orientation
     private int mLayoutOrientation = VERTICAL;
     private boolean mReverseLayout = false;
+    @Size
     private int mSize = CarUiRecyclerView.SIZE_LARGE;
-    @Nullable
+    @NonNull
     private SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
 
     /**
@@ -57,7 +57,7 @@
         return layoutStyle;
     }
 
-    /** Returns number of recyclerview spans */
+    @Override
     public int getSpanCount() {
         return mSpanCount;
     }
@@ -70,24 +70,22 @@
         mSpanCount = spanCount;
     }
 
-    /** Returns {@link CarUiRecyclerViewLayout} */
-    @CarUiRecyclerViewLayout
+    @Override
     public int getLayoutType() {
         return CarUiRecyclerViewLayout.GRID;
     }
 
-    /** Returns layout direction {@link Orientation} */
-    @Orientation
+    @Override
     public int getOrientation() {
         return mLayoutOrientation;
     }
 
-    /** sets layout direction {@link Orientation} */
+    /** sets layout direction {@link CarUiLayoutStyle.Orientation} */
     public void setOrientation(@Orientation int orientation) {
         mLayoutOrientation = orientation;
     }
 
-    /** Returns true if layout is reversed */
+    @Override
     public boolean getReverseLayout() {
         return mReverseLayout;
     }
@@ -98,7 +96,7 @@
     }
 
     /** Returns a wrapper {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup} */
-    @Nullable
+    @NonNull
     public SpanSizeLookup getSpanSizeLookup() {
         return mSpanSizeLookup;
     }
@@ -108,9 +106,7 @@
         mSpanSizeLookup = spanSizeLookup;
     }
 
-    /**
-     * @return CarUiRecyclerView size
-     */
+    @Override
     public int getSize() {
         return mSize;
     }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLayoutStyle.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLayoutStyle.java
index 60a38c9..597d8e6 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLayoutStyle.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLayoutStyle.java
@@ -16,10 +16,13 @@
 package com.android.car.ui.recyclerview;
 
 import android.widget.LinearLayout;
+
 import androidx.annotation.IntDef;
 import androidx.recyclerview.widget.RecyclerView.LayoutManager;
 
 import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout;
+import com.android.car.ui.recyclerview.CarUiRecyclerView.Size;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -50,5 +53,6 @@
     boolean getReverseLayout();
 
     /** Returns CarUiRecyclerView size */
+    @Size
     int getSize();
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLinearLayoutStyle.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLinearLayoutStyle.java
index 4d0d7a9..1a3ec9c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLinearLayoutStyle.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiLinearLayoutStyle.java
@@ -17,20 +17,21 @@
 
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.LayoutManager;
 
 import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout;
+import com.android.car.ui.recyclerview.CarUiRecyclerView.Size;
 
 /**
  * CarUi proxy class for {@link LinearLayoutManager}
  */
 public final class CarUiLinearLayoutStyle implements CarUiLayoutStyle {
 
-    @CarUiRecyclerViewLayout
-    private  int mLayoutType = CarUiRecyclerViewLayout.LINEAR;
     @Orientation
     private int mLayoutOrientation = VERTICAL;
     private boolean mReverseLayout = false;
+    @Size
     private int mSize = CarUiRecyclerView.SIZE_LARGE;
 
     /**
@@ -45,34 +46,39 @@
         }
 
         CarUiLinearLayoutStyle layoutStyle = new CarUiLinearLayoutStyle();
-        layoutStyle.setOrientation(((LinearLayoutManager) layoutManager).getOrientation());
+        switch (((LinearLayoutManager) layoutManager).getOrientation()) {
+            case RecyclerView.HORIZONTAL:
+                layoutStyle.setOrientation(CarUiLayoutStyle.HORIZONTAL);
+                break;
+            case RecyclerView.VERTICAL:
+                layoutStyle.setOrientation(CarUiLayoutStyle.VERTICAL);
+                break;
+        }
         layoutStyle.setReverseLayout(((LinearLayoutManager) layoutManager).getReverseLayout());
         return layoutStyle;
     }
 
-    /** Returns number of recyclerview spans */
+    @Override
     public int getSpanCount() {
         return 1;
     }
 
-    /** Returns {@link CarUiRecyclerViewLayout} */
-    @CarUiRecyclerViewLayout
+    @Override
     public int getLayoutType() {
         return CarUiRecyclerViewLayout.LINEAR;
     }
 
-    /** Returns layout direction {@link Orientation} */
-    @Orientation
+    @Override
     public int getOrientation() {
         return mLayoutOrientation;
     }
 
-    /** sets layout direction {@link Orientation} */
+    /** sets layout direction {@link CarUiLayoutStyle.Orientation} */
     public void setOrientation(@Orientation int orientation) {
         mLayoutOrientation = orientation;
     }
 
-    /** Returns true if layout is reversed */
+    @Override
     public boolean getReverseLayout() {
         return mReverseLayout;
     }
@@ -82,9 +88,7 @@
         mReverseLayout = reverseLayout;
     }
 
-    /**
-     * @return CarUiRecyclerView size
-     */
+    @Override
     public int getSize() {
         return mSize;
     }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
index 39926e1..f69044b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapter.java
@@ -16,10 +16,13 @@
 
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,6 +38,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
+import com.android.car.ui.SecureView;
 import com.android.car.ui.widget.CarUiTextView;
 
 import java.util.List;
@@ -46,7 +50,11 @@
  * <ul>
  * <li> Implements {@link CarUiRecyclerView.ItemCap} - defaults to unlimited item count.
  * </ul>
+ * <p>
+ * Rendered views will comply with
+ * <a href="https://source.android.com/devices/automotive/hmi/car_ui/appendix_b">customization guardrails</a>
  */
+@TargetApi(MIN_TARGET_API)
 public class CarUiListItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements
         CarUiRecyclerView.ItemCap {
 
@@ -244,6 +252,29 @@
                 mIconContainer.setVisibility(View.GONE);
             }
 
+            boolean logWarning = false;
+            if (mTouchInterceptor instanceof SecureView) {
+                ((SecureView) mTouchInterceptor).setSecure(item.isSecure());
+            } else {
+                logWarning = true;
+            }
+
+            if (mReducedTouchInterceptor instanceof SecureView) {
+                ((SecureView) mReducedTouchInterceptor).setSecure(item.isSecure());
+            } else {
+                logWarning = true;
+            }
+
+            if (mActionContainerTouchInterceptor instanceof SecureView) {
+                ((SecureView) mActionContainerTouchInterceptor).setSecure(item.isSecure());
+            } else {
+                logWarning = true;
+            }
+
+            if (logWarning) {
+                Log.w("carui", "List item doesn't have a SecureView, but security was requested!");
+            }
+
             mActionDivider.setVisibility(
                     item.isActionDividerVisible() ? View.VISIBLE : View.GONE);
             mSwitch.setVisibility(View.GONE);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapterAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapterAdapterV1.java
index 82d0f6d..6c9b349 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapterAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiListItemAdapterAdapterV1.java
@@ -18,12 +18,10 @@
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterDataObserverOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
 
 /**
  * Wrapper class that passes the data to car-ui via AdapterOEMV1 interface
@@ -34,73 +32,9 @@
     @NonNull
     private AdapterOEMV1 mAdapter;
 
-    @NonNull
-    private final AdapterDataObserverOEMV1 mAdapterDataObserver = new AdapterDataObserverOEMV1() {
-        @Override
-        public void onChanged() {
-            CarUiListItemAdapterAdapterV1.super.notifyDataSetChanged();
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            CarUiListItemAdapterAdapterV1.super.notifyItemRangeChanged(positionStart, itemCount);
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount,
-                @Nullable Object payload) {
-            CarUiListItemAdapterAdapterV1.super.notifyItemRangeChanged(positionStart, itemCount,
-                    payload);
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            CarUiListItemAdapterAdapterV1.super.notifyItemRangeInserted(positionStart, itemCount);
-        }
-
-        @Override
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            CarUiListItemAdapterAdapterV1.super.notifyItemRangeRemoved(positionStart, itemCount);
-        }
-
-        @Override
-        public void onItemMoved(int fromPosition, int toPosition) {
-            CarUiListItemAdapterAdapterV1.super.notifyItemMoved(fromPosition, toPosition);
-        }
-
-        @Override
-        public void onStateRestorationPolicyChanged() {
-            CarUiListItemAdapterAdapterV1.this.updateStateRestorationPolicy();
-        }
-    };
-
     public CarUiListItemAdapterAdapterV1(@NonNull AdapterOEMV1 adapter) {
         this.mAdapter = adapter;
         CarUiListItemAdapterAdapterV1.super.setHasStableIds(adapter.hasStableIds());
-        updateStateRestorationPolicy();
-    }
-
-    private void updateStateRestorationPolicy() {
-        switch (mAdapter.getStateRestorationPolicyInt()) {
-            case 2:
-                CarUiListItemAdapterAdapterV1.super.setStateRestorationPolicy(
-                        RecyclerView.Adapter.StateRestorationPolicy.PREVENT);
-                break;
-            case 1:
-                CarUiListItemAdapterAdapterV1.super.setStateRestorationPolicy(
-                        RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
-                break;
-            case 0:
-            default:
-                CarUiListItemAdapterAdapterV1.super.setStateRestorationPolicy(
-                        RecyclerView.Adapter.StateRestorationPolicy.ALLOW);
-        }
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
-        throw new IllegalStateException(
-                "OEM implementation of adapter can only be used with an OEM list view");
     }
 
     @Override
@@ -149,19 +83,15 @@
     }
 
     @Override
-    public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
-        if (!super.hasObservers()) {
-            mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
-        }
-        super.registerAdapterDataObserver(observer);
+    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        // TODO: can we return something other than null here?
+        mAdapter.onAttachedToRecyclerView(null);
     }
 
     @Override
-    public void unregisterAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
-        super.unregisterAdapterDataObserver(observer);
-        if (!super.hasObservers()) {
-            mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
-        }
+    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+        // TODO: can we return something other than null here?
+        mAdapter.onDetachedFromRecyclerView(null);
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
index 2081db6..f6616ca 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRadioButtonListItemAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.recyclerview;
 
+import android.annotation.SuppressLint;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -69,6 +70,8 @@
     }
 
     @Override
+    @SuppressLint("Correctness")
+    // TODO: (b/200168151)
     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
         if (holder.getItemViewType() == VIEW_TYPE_LIST_ITEM) {
             if (!(holder instanceof RadioButtonListItemViewHolder)) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index e038d09..5c93558 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -16,18 +16,39 @@
 
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
 
+import androidx.annotation.IdRes;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
+import androidx.recyclerview.widget.RecyclerView.OnFlingListener;
+import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import java.lang.annotation.Retention;
 
@@ -36,24 +57,16 @@
  * customizable by OEM.
  * <p>
  * This is the base class for CarUiRecyclerView implementation.
+ * <p>
+ * Rendered views will comply with
+ * <a href="https://source.android.com/devices/automotive/hmi/car_ui/appendix_b">customization guardrails</a>
  */
-public abstract class CarUiRecyclerView extends RecyclerView {
-
-    /**
-     * Use this method to create an instance of CarUiRecyclerView at runtime.
-     */
-    public static CarUiRecyclerView create(Context context) {
-        return SharedLibraryFactorySingleton.get(context)
-                .createRecyclerView(context, null);
-    }
-
-    /**
-     * Use this method to create an instance of CarUiRecyclerView at runtime.
-     */
-    public static CarUiRecyclerView create(Context context, AttributeSet attributeSet) {
-        return SharedLibraryFactorySingleton.get(context)
-                .createRecyclerView(context, attributeSet);
-    }
+@TargetApi(MIN_TARGET_API)
+@SuppressLint("Instantiatable")
+public interface CarUiRecyclerView {
+    int SIZE_SMALL = 0;
+    int SIZE_MEDIUM = 1;
+    int SIZE_LARGE = 2;
 
     /**
      * Describes the expected relative size of the {@link CarUiRecyclerView}. The list may be
@@ -61,24 +74,21 @@
      */
     @Retention(SOURCE)
     @IntDef({SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
-    public @interface Size {}
-    public static final int SIZE_SMALL = 0;
-    public static final int SIZE_MEDIUM = 1;
-    public static final int SIZE_LARGE = 2;
+    @interface Size {}
 
     /**
      * The possible values for setScrollBarPosition. The default value is {@link
      * CarUiRecyclerViewLayout#LINEAR}.
      */
     @IntDef({
-            CarUiRecyclerViewLayout.LINEAR,
-            CarUiRecyclerViewLayout.GRID,
+        CarUiRecyclerViewLayout.LINEAR,
+        CarUiRecyclerViewLayout.GRID,
     })
     @Retention(SOURCE)
-    public @interface CarUiRecyclerViewLayout {
+    @interface CarUiRecyclerViewLayout {
         /**
-         * Arranges items either horizontally in a single row or vertically in a single column. This
-         * is default.
+         * Arranges items either horizontally in a single row or vertically in a single column.
+         * This is the default value.
          */
         int LINEAR = 0;
 
@@ -103,184 +113,388 @@
      * }
      * }</pre>
      */
-    public interface ItemCap {
-
-        /**
-         * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
-         */
-        int UNLIMITED = -1;
-
+    interface ItemCap {
         /**
          * Sets the maximum number of items available in the adapter. A value less than '0' means
          * the list should not be capped.
          */
         void setMaxItems(int maxItems);
-    }
-
-    public CarUiRecyclerView(Context context) {
-        super(context);
-    }
-
-    public CarUiRecyclerView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public CarUiRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
+        /**
+         * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+         */
+        int UNLIMITED = -1;
     }
 
     /**
-     * Return's the container which contains the scrollbar and this RecyclerView.
+     * The RecyclerView is not currently scrolling.
+     *
+     * @see #getScrollState()
      */
-    public abstract View getContainer();
+    int SCROLL_STATE_IDLE = 0;
 
     /**
-     * Set the {@link LayoutManager} that this RecyclerView will use.
+     * The RecyclerView is currently being dragged by outside input such as user touch input.
      *
-     * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
-     * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom layout
-     * arrangements for child views. These arrangements are controlled by the {@link LayoutManager}.
-     * A LayoutManager must be provided for RecyclerView to function.</p>
+     * @see #getScrollState()
+     */
+    int SCROLL_STATE_DRAGGING = 1;
+
+    /**
+     * The RecyclerView is currently animating to a final position while not under
+     * outside control.
      *
-     * <p>Several default strategies are provided for common uses such as lists and grids.</p>
-     *
-     * @param layoutManager LayoutManager to use
-     * @deprecated to be implemented by OEM
+     * @see #getScrollState()
+     */
+    int SCROLL_STATE_SETTLING = 2;
+
+    /**
+     * Equivalent of {@link RecyclerView.OnScrollListener}
+     */
+    interface OnScrollListener {
+        void onScrolled(CarUiRecyclerView recyclerView, int dx, int dy);
+        void onScrollStateChanged(CarUiRecyclerView recyclerView, int newState);
+    }
+
+    /**
+     * Adapters that implement this interface will receive the calls
+     */
+    interface OnAttachListener {
+        void onAttachedToCarUiRecyclerView(@NonNull CarUiRecyclerView carUiRecyclerView);
+        void onDetachedFromCarUiRecyclerView(@NonNull CarUiRecyclerView carUiRecyclerView);
+    }
+
+    /**
+     * Use this method to create an instance of CarUiRecyclerView at runtime.
+     */
+    static CarUiRecyclerView create(Context context) {
+        return PluginFactorySingleton.get(context)
+            .createRecyclerView(context, null);
+    }
+
+    /**
+     * Use this method to create an instance of CarUiRecyclerView at runtime.
+     */
+    static CarUiRecyclerView create(Context context, AttributeSet attributeSet) {
+        return PluginFactorySingleton.get(context)
+            .createRecyclerView(context, attributeSet);
+    }
+
+    /**
+     * see {@link RecyclerView#addItemDecoration(ItemDecoration)}
+     * @deprecated this will fail when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public void setLayoutManager(@Nullable LayoutManager layoutManager) {
-        super.setLayoutManager(layoutManager);
-    }
+    void addItemDecoration(@NonNull ItemDecoration decor);
 
     /**
-     * Return the {@link LayoutManager} currently responsible for
-     * layout policy for this RecyclerView.
-     *
-     * @return The currently bound LayoutManager
-     * @deprecated will return null for OEM implementation.
+     * see {@link RecyclerView#addItemDecoration(ItemDecoration, int)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    void addItemDecoration(@NonNull ItemDecoration decor, int index);
+
+    /** see {@link RecyclerView#addOnChildAttachStateChangeListener} */
+    void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener);
+
+    /** see {@link View#addOnLayoutChangeListener(OnLayoutChangeListener)} */
+    void addOnLayoutChangeListener(OnLayoutChangeListener listener);
+
+    /** see {@link RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener)} */
+    void addOnScrollListener(OnScrollListener scrollListener);
+
+    /** {@link RecyclerView#clearOnChildAttachStateChangeListeners()} */
+    void clearOnChildAttachStateChangeListeners();
+
+    /** {@link RecyclerView#clearOnScrollListeners()} */
+    void clearOnScrollListeners();
+
+    /** see {@link LinearLayoutManager#findFirstCompletelyVisibleItemPosition()} */
+    int findFirstCompletelyVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findFirstVisibleItemPosition()} */
+    int findFirstVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findLastCompletelyVisibleItemPosition()} */
+    int findLastCompletelyVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findLastVisibleItemPosition()} */
+    int findLastVisibleItemPosition();
+
+    /**
+     * see {@link View#findViewById(int)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    <T extends View> T findViewById(int id);
+
+    /** see {@link RecyclerView#findViewHolderForAdapterPosition(int)} */
+    ViewHolder findViewHolderForAdapterPosition(int position);
+
+    /** see {@link RecyclerView#findViewHolderForAdapterPosition(int)} */
+    ViewHolder findViewHolderForLayoutPosition(int position);
+
+    /** see {@link ViewGroup#focusableViewAvailable(View)} */
+    void focusableViewAvailable(View v);
+
+    /** see {@link RecyclerView#getAdapter()} */
+    Adapter<?> getAdapter();
+
+    /** see {@link View#getAlpha()} */
+    float getAlpha();
+
+    /**
+     * see {@link ViewGroup#getChildAt(int)}
+     * @deprecated this will fail when there is a oem implementation
      */
     @Deprecated
     @Nullable
-    @Override
-    public LayoutManager getLayoutManager() {
-        return super.getLayoutManager();
-    }
+    View getChildAt(int index);
 
     /**
-     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can affect both
-     * measurement and drawing of individual item views.
-     *
-     * <p>Item decorations are ordered. Decorations placed earlier in the list will
-     * be run/queried/drawn first for their effects on item views. Padding added to views will be
-     * nested; a padding added by an earlier decoration will mean further item decorations in the
-     * list will be asked to draw/pad within the previous decoration's given area.</p>
-     *
-     * @param decor Decoration to add
-     * @deprecated to be implemented by OEMs
+     * see {@link ViewGroup#getChildCount()}
+     * @deprecated this will fail when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public void addItemDecoration(@NonNull ItemDecoration decor) {
-        super.addItemDecoration(decor);
-    }
+    int getChildCount();
 
     /**
-     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can affect both
-     * measurement and drawing of individual item views.
-     *
-     * <p>Item decorations are ordered. Decorations placed earlier in the list will
-     * be run/queried/drawn first for their effects on item views. Padding added to views will be
-     * nested; a padding added by an earlier decoration will mean further item decorations in the
-     * list will be asked to draw/pad within the previous decoration's given area.</p>
-     *
-     * @param decor Decoration to add
-     * @param index Position in the decoration chain to insert this decoration at. If this value is
-     *              negative the decoration will be added at the end.
-     * @deprecated to be implemented by OEM
+     * see {@link RecyclerView#getChildLayoutPosition(View)}
      */
-    @Deprecated
-    @Override
-    public void addItemDecoration(@NonNull ItemDecoration decor, int index) {
-        super.addItemDecoration(decor, index);
-    }
+    int getChildLayoutPosition(View child);
+
+    /** see {@link View#getContentDescription()} */
+    CharSequence getContentDescription();
+
+    /** see {@link View#getContext()} */
+    Context getContext();
+
+    /** see {@link OrientationHelper#getEndAfterPadding()} */
+    int getEndAfterPadding();
+
+    /** see {@link View#getHeight()} */
+    int getHeight();
 
     /**
-     * Returns an {@link ItemDecoration} previously added to this RecyclerView.
-     *
-     * @param index The index position of the desired ItemDecoration.
-     * @return the ItemDecoration at index position
-     * @throws IndexOutOfBoundsException on invalid index
-     * @deprecated to be handled by OEM
+     * see {@link RecyclerView#getItemDecorationAt(int)}
+     * @deprecated this will fail when there is a oem implementation
      */
     @Deprecated
-    @Override
+    @Nullable
+    ItemDecoration getItemDecorationAt(int index);
+
+    /**
+     * see {@link RecyclerView#getItemDecorationCount()}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    int getItemDecorationCount();
+
+    /**
+     * see {@link RecyclerView#getLayoutManager()}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    @Nullable
+    LayoutManager getLayoutManager();
+
+    /** Use this instead of {@link #getLayoutManager} */
+    @Nullable
+    CarUiLayoutStyle getLayoutStyle();
+
+    /** {@link RecyclerView#hasFixedSize()} */
+    boolean hasFixedSize();
+
+    /** see {@link View#getPaddingLeft()} */
+    int getPaddingLeft();
+
+    /** see {@link View#getPaddingRight()} */
+    int getPaddingRight();
+
+    /** see {@link View#getPaddingBottom()} */
+    int getPaddingBottom();
+
+    /** see {@link View#getPaddingEnd()} */
+    int getPaddingEnd();
+
+    /** see {@link View#getPaddingStart()} */
+    int getPaddingStart();
+
+    /** see {@link View#getPaddingTop()} */
+    int getPaddingTop();
+
+    /**
+     * see {@link View#getParent()}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    ViewParent getParent();
+
+    /**
+     * see {@link LayoutManager#getChildCount()}
+     * Prefer this method over {@link #getChildCount()}
+     */
+    int getRecyclerViewChildCount();
+
+    /**
+     * see {@link LayoutManager#getChildAt(int)}
+     * Prefer this method over {@link #getChildAt(int)}
+     */
+    @Nullable
+    View getRecyclerViewChildAt(int index);
+
+    /** see {@link RecyclerView#getScrollState()} */
+    int getScrollState();
+
+    /** see {@link OrientationHelper#getStartAfterPadding()} */
+    int getStartAfterPadding();
+
+    /** see {@link View#getTag()} */
+    Object getTag();
+
+    /** see {@link OrientationHelper#getTotalSpace()} */
+    int getTotalSpace();
+
+    /**
+     * Returns a view that will be attached to the view hierarchy.
+     */
     @NonNull
-    public ItemDecoration getItemDecorationAt(int index) {
-        return super.getItemDecorationAt(index);
-    }
+    View getView();
+
+    /** see {@link View#getViewTreeObserver()} */
+    ViewTreeObserver getViewTreeObserver();
 
     /**
-     * Returns the number of {@link ItemDecoration} currently added to this RecyclerView.
-     *
-     * @return number of ItemDecorations currently added added to this RecyclerView.
-     * @deprecated to be handled by OEM
+     * see {@link RecyclerView#invalidateItemDecorations()}
+     * @deprecated this will fail value when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public int getItemDecorationCount() {
-        return super.getItemDecorationCount();
-    }
+    void invalidateItemDecorations();
+
+    /** see {@link View#post(Runnable)} */
+    boolean post(Runnable runnable);
 
     /**
-     * Removes the {@link ItemDecoration} associated with the supplied index position.
-     *
-     * @param index The index position of the ItemDecoration to be removed.
-     * @deprecated to be handled by OEM
+     * see {@link RecyclerView#removeItemDecoration(ItemDecoration)}
+     * @deprecated this will fail value when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public void removeItemDecorationAt(int index) {
-        super.removeItemDecorationAt(index);
-    }
+    void removeItemDecoration(@NonNull ItemDecoration decor);
 
     /**
-     * Remove an {@link ItemDecoration} from this RecyclerView.
-     *
-     * <p>The given decoration will no longer impact the measurement and drawing of
-     * item views.</p>
-     *
-     * @param decor Decoration to remove
-     * @see #addItemDecoration(ItemDecoration)
-     * @deprecated to be handled by OEM
+     * see {@link RecyclerView#removeItemDecorationAt(int)}
+     * @deprecated this will fail value when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public void removeItemDecoration(@NonNull ItemDecoration decor) {
-        super.removeItemDecoration(decor);
-    }
+    void removeItemDecorationAt(int index);
+
+    /** see {@link RecyclerView#removeOnChildAttachStateChangeListener} */
+    void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener);
+
+    /** see {@link View#removeOnLayoutChangeListener(OnLayoutChangeListener)} */
+    void removeOnLayoutChangeListener(OnLayoutChangeListener listener);
+
+    /** see {@link View#requestLayout()} */
+    void requestLayout();
+
+    /** see {@link RecyclerView#removeOnScrollListener(OnScrollListener)} */
+    void removeOnScrollListener(OnScrollListener scrollListener);
+
+    /** see {@link View#requireViewById(int)} */
+    <T extends View> T requireViewById(int id);
+
+    /** see {@link View#scrollBy(int, int)} */
+    void scrollBy(int x, int y);
+
+    /** see {@link RecyclerView#scrollToPosition(int)} */
+    void scrollToPosition(int position);
+
+    /** see {@link RecyclerView#setAdapter(Adapter)} */
+    void setAdapter(Adapter<?> adapter);
+
+    /** see {@link View#setAlpha(float)} */
+    void setAlpha(float alpha);
+
+    /** see {@link RecyclerView#setClipToPadding(boolean)} */
+    void setClipToPadding(boolean enabled);
+
+    /** see {@link View#setContentDescription(CharSequence)} */
+    void setContentDescription(CharSequence contentDescription);
+
+    /** see {@link RecyclerView#setHasFixedSize(boolean)} */
+    void setHasFixedSize(boolean hasFixedSize);
 
     /**
-     * @deprecated this will return incorrect value when there is a oem implementation
-     */
-    @Override
-    @Deprecated
-    public int getChildCount() {
-        return super.getChildCount();
-    }
-
-    /**
-     * @deprecated this will return incorrect value when there is a oem implementation
+     * see {@link View#setFadingEdgeLength(int)}
+     * @deprecated this will fail when there is a oem implementation
      */
     @Deprecated
-    @Override
-    public View getChildAt(int index) {
-        return super.getChildAt(index);
-    }
+    void setFadingEdgeLength(int length);
+
+    /** see {@link RecyclerView#setOnFlingListener(OnFlingListener)} */
+    void setOnFlingListener(OnFlingListener listener);
+
+    /** see {@link View#setId(int)}  */
+    void setId(@IdRes int id);
 
     /**
-     * Use this instead of setLayoutManager
-     * @param layoutStyle
+     * see {@link RecyclerView#setItemAnimator(ItemAnimator)}
+     * @deprecated this will fail when there is a oem implementation
      */
-    public abstract void setLayoutStyle(CarUiLayoutStyle layoutStyle);
+    @Deprecated
+    void setItemAnimator(ItemAnimator itemAnimator);
+
+    /**
+     * see {@link RecyclerView#setLayoutManager(LayoutManager)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    default void setLayoutManager(@Nullable LayoutManager layoutManager) {}
+
+    /** Use this instead of {@link #setLayoutManager} */
+    void setLayoutStyle(@Nullable CarUiLayoutStyle layoutStyle);
+
+    /** see {@link View#setOnTouchListener(OnTouchListener)} */
+    void setOnTouchListener(OnTouchListener listener);
+
+    /** see {@link View#setPadding(int, int, int, int)} */
+    void setPadding(int left, int top, int right, int bottom);
+
+    /** see {@link View#setPaddingRelative(int, int, int, int)} */
+    void setPaddingRelative(int start, int top, int end, int bottom);
+
+    /** Will get called when {@link SpanSizeLookup} changes at runtime. */
+    void setSpanSizeLookup(@NonNull SpanSizeLookup spanSizeLookup);
+
+    /** see {@link View#setTag(Object)} */
+    void setTag(Object tag);
+
+    /**
+     * see {@link View#setVerticalFadingEdgeEnabled(boolean)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    void setVerticalFadingEdgeEnabled(boolean enabled);
+
+    /**
+     * see {@link View#setVerticalScrollBarEnabled(boolean)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    void setVerticalScrollBarEnabled(boolean enabled);
+
+    /**
+     * see {@link View#setVerticalScrollbarPosition(int)}
+     * @deprecated this will fail when there is a oem implementation
+     */
+    @Deprecated
+    void setVerticalScrollbarPosition(int position);
+
+    /** see {@link View#setVisibility(int)} */
+    void setVisibility(int visible);
+
+    /** see {@link RecyclerView#smoothScrollBy(int, int)} */
+    void smoothScrollBy(int dx, int dy);
+
+    /** see {@link RecyclerView#smoothScrollToPosition(int) */
+    void smoothScrollToPosition(int position);
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java
deleted file mode 100644
index 2f2a5b1..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewAdapter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.ui.recyclerview;
-
-import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.car.ui.R;
-
-/** The adapter for the parent recyclerview in {@link CarUiRecyclerView} widget. */
-final class CarUiRecyclerViewAdapter
-        extends RecyclerView.Adapter<CarUiRecyclerViewAdapter.NestedRowViewHolder> {
-
-    @Override
-    public CarUiRecyclerViewAdapter.NestedRowViewHolder onCreateViewHolder(
-            ViewGroup parent, int viewType) {
-        View v =
-                LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.car_ui_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(@NonNull NestedRowViewHolder holder, int position) {
-    }
-
-    // Return the size of your data set (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 final FrameLayout frameLayout;
-
-        NestedRowViewHolder(View view) {
-            super(view);
-            frameLayout = findViewByRefId(view, R.id.nested_recycler_view_layout);
-        }
-    }
-}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
index 4e9dc55..4bd136a 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewContainer.java
@@ -13,44 +13,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.car.ui.recyclerview;
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.View;
 import android.widget.FrameLayout;
 
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.R;
+
 /**
- * Container that contains the scrollbar and RecyclerView when scrollbar is enabled.
- *
- * This container is required to expose addViewInLayout such that the scrollbar and be added without
- * triggering multiple invalidate and relayout calls.
+ * @deprecated Only maintained for legacy reasons.
+ * Historically this class used to contain the CarUiRecyclerView, and ScrollBar.
+ * This class can't be removed because OEMs might have overlaid car_ui_recycler_view.xml with their
+ * RROs.
+ * Now this class is only an FrameLayout that contains a {@link RecyclerView} to maintain backwards
+ * compatibility.
  */
+@Deprecated
 public class CarUiRecyclerViewContainer extends FrameLayout {
 
     public CarUiRecyclerViewContainer(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public CarUiRecyclerViewContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public CarUiRecyclerViewContainer(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-    }
-
-    public CarUiRecyclerViewContainer(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * Adds the recyclerview using addViewInLayout so that invalidate and relayout calls are not
-     * triggered.
-     */
-    void addRecyclerView(View view, LayoutParams layoutParams) {
-        addViewInLayout(view, 0, layoutParams);
+        // We have to inflate this from layout because some of the attributes e.g.
+        // android:scrollbars can't be set from code.
+        inflate(context, R.layout.car_ui_recycler_view_only, this);
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewImpl.java
index 084a7d0..8148e6f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerViewImpl.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
 import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
@@ -22,11 +23,10 @@
 import static com.android.car.ui.utils.ViewUtils.LazyLayoutView;
 import static com.android.car.ui.utils.ViewUtils.setRotaryScrollEnabled;
 
+import android.annotation.SuppressLint;
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.InputDevice;
@@ -34,26 +34,38 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
 import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.OrientationHelper;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
+import androidx.recyclerview.widget.RecyclerView.OnFlingListener;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import com.android.car.ui.R;
+import com.android.car.ui.preference.PreferenceFragment.AndroidxRecyclerViewProvider;
 import com.android.car.ui.recyclerview.decorations.grid.GridDividerItemDecoration;
-import com.android.car.ui.recyclerview.decorations.grid.GridOffsetItemDecoration;
 import com.android.car.ui.recyclerview.decorations.linear.LinearDividerItemDecoration;
-import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration;
-import com.android.car.ui.recyclerview.decorations.linear.LinearOffsetItemDecoration.OffsetPosition;
 import com.android.car.ui.utils.CarUxRestrictionsUtil;
 
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
@@ -62,9 +74,22 @@
  * 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 CarUiRecyclerViewImpl extends CarUiRecyclerView implements LazyLayoutView {
+@SuppressLint("CustomViewStyleable")
+@RequiresApi(MIN_TARGET_API)
+public final class CarUiRecyclerViewImpl extends FrameLayout
+        implements CarUiRecyclerView, LazyLayoutView, AndroidxRecyclerViewProvider {
 
     private static final String TAG = "CarUiRecyclerView";
+    /**
+     * exact copy of {@link Recyclerview#LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE}
+     */
+    private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
+            new Class<?>[]{Context.class, AttributeSet.class, int.class, int.class};
+
+    @Nullable
+    private Adapter<?> mAdapter;
+    @NonNull
+    private RecyclerView mRecyclerView;
 
     private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
             new UxRestrictionChangedListener();
@@ -78,28 +103,11 @@
     private int mScrollBarPaddingBottom;
     @Nullable
     private ScrollBar mScrollBar;
-
-    @Nullable
-    private GridOffsetItemDecoration mTopOffsetItemDecorationGrid;
-    @Nullable
-    private GridOffsetItemDecoration mBottomOffsetItemDecorationGrid;
-    @Nullable
-    private RecyclerView.ItemDecoration mTopOffsetItemDecorationLinear;
-    @Nullable
-    private RecyclerView.ItemDecoration mBottomOffsetItemDecorationLinear;
     @Nullable
     private GridDividerItemDecoration mDividerItemDecorationGrid;
     @Nullable
-    private RecyclerView.ItemDecoration mDividerItemDecorationLinear;
+    private ItemDecoration mDividerItemDecorationLinear;
     private int mNumOfColumns;
-    private boolean mInstallingExtScrollBar = false;
-    private int mContainerVisibility = View.VISIBLE;
-    @Nullable
-    private Rect mContainerPadding;
-    @Nullable
-    private Rect mContainerPaddingRelative;
-    @Nullable
-    private ViewGroup mContainer;
     @Size
     private int mSize;
 
@@ -107,27 +115,40 @@
     private boolean mIsInitialized;
     private boolean mEnableDividers;
 
-    private boolean mHasScrolled = false;
-
     @NonNull
     private final Set<Runnable> mOnLayoutCompletedListeners = new HashSet<>();
 
-    private OnScrollListener mOnScrollListener = new OnScrollListener() {
-        @Override
-        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-            if (dx > 0 || dy > 0) {
-                mHasScrolled = true;
-                removeOnScrollListener(this);
-            }
-        }
-    };
+    @Nullable
+    private CarUiLayoutStyle mLayoutStyle;
+
+    @NonNull
+    private final List<OnScrollListener> mScrollListeners = new ArrayList<>();
+
+    @NonNull
+    private final RecyclerView.OnScrollListener mOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+                    for (OnScrollListener listener : mScrollListeners) {
+                        listener.onScrollStateChanged(CarUiRecyclerViewImpl.this,
+                                toInternalScrollState(newState));
+                    }
+                }
+
+                @Override
+                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                    for (OnScrollListener listener : mScrollListeners) {
+                        listener.onScrolled(CarUiRecyclerViewImpl.this, dx, dy);
+                    }
+                }
+            };
 
     public CarUiRecyclerViewImpl(@NonNull Context context) {
         this(context, null);
     }
 
     public CarUiRecyclerViewImpl(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, R.attr.carUiRecyclerViewStyle);
+        this(context, attrs, 0);
     }
 
     public CarUiRecyclerViewImpl(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -137,16 +158,56 @@
         init(context, attrs, defStyle);
     }
 
+    @Override
+    public boolean canScrollHorizontally(int direction) {
+        return mRecyclerView.canScrollHorizontally(direction);
+    }
+
+    @Override
+    public boolean canScrollVertically(int direction) {
+        return mRecyclerView.canScrollVertically(direction);
+    }
+
     private void init(Context context, AttributeSet attrs, int defStyleAttr) {
-        setClipToPadding(false);
         TypedArray a = context.obtainStyledAttributes(
                 attrs,
                 R.styleable.CarUiRecyclerView,
                 defStyleAttr,
-                R.style.Widget_CarUi_CarUiRecyclerView);
-        initRotaryScroll(a);
+                0);
 
         mScrollBarEnabled = context.getResources().getBoolean(R.bool.car_ui_scrollbar_enable);
+        @LayoutRes int layout = R.layout.car_ui_recycler_view_no_scrollbar;
+
+        mSize = a.getInt(R.styleable.CarUiRecyclerView_carUiSize, SIZE_LARGE);
+        if (mScrollBarEnabled) {
+            switch (mSize) {
+                case SIZE_SMALL:
+                    layout = R.layout.car_ui_recycler_view_small;
+                    break;
+                case SIZE_MEDIUM:
+                    layout = R.layout.car_ui_recycler_view_medium;
+                    break;
+                case SIZE_LARGE:
+                    layout = R.layout.car_ui_recycler_view;
+            }
+        }
+
+        LayoutInflater factory = LayoutInflater.from(context);
+        View rootView = factory.inflate(layout, this, true);
+        ViewGroup recyclerViewContainer = requireViewByRefId(rootView, R.id.car_ui_recycler_view);
+        if (recyclerViewContainer instanceof CarUiRecyclerViewContainer) {
+            // To keep backwards compatibility CarUiRecyclerViewContainer is a FrameLayout
+            // that has a RecyclerView at index 0
+            mRecyclerView = (RecyclerView) recyclerViewContainer.getChildAt(0);
+        } else {
+            mRecyclerView = (RecyclerView) recyclerViewContainer;
+        }
+
+        boolean rotaryScrollEnabled = a.getBoolean(
+                R.styleable.CarUiRecyclerView_rotaryScrollEnabled, /* defValue=*/ false);
+        int orientation = a.getInt(R.styleable.CarUiRecyclerView_android_orientation,
+                LinearLayout.VERTICAL);
+        initRotaryScroll(mRecyclerView, rotaryScrollEnabled, orientation);
 
         mScrollBarPaddingTop = context.getResources()
                 .getDimensionPixelSize(R.dimen.car_ui_scrollbar_padding_top);
@@ -160,63 +221,29 @@
                 a.getBoolean(R.styleable.CarUiRecyclerView_enableDivider, /* defValue= */ false);
 
         mDividerItemDecorationLinear = new LinearDividerItemDecoration(
-                context.getDrawable(R.drawable.car_ui_recyclerview_divider));
+                ContextCompat.getDrawable(context, R.drawable.car_ui_recyclerview_divider));
 
         mDividerItemDecorationGrid =
                 new GridDividerItemDecoration(
-                        context.getDrawable(R.drawable.car_ui_divider),
-                        context.getDrawable(R.drawable.car_ui_divider),
+                        ContextCompat.getDrawable(context, R.drawable.car_ui_divider),
+                        ContextCompat.getDrawable(context, R.drawable.car_ui_divider),
                         mNumOfColumns);
 
-        mTopOffsetItemDecorationLinear =
-                new LinearOffsetItemDecoration(0, OffsetPosition.START);
-        mBottomOffsetItemDecorationLinear =
-                new LinearOffsetItemDecoration(0, OffsetPosition.END);
-        mTopOffsetItemDecorationGrid =
-                new GridOffsetItemDecoration(0, mNumOfColumns,
-                        OffsetPosition.START);
-        mBottomOffsetItemDecorationGrid =
-                new GridOffsetItemDecoration(0, mNumOfColumns,
-                        OffsetPosition.END);
-
         mIsInitialized = true;
 
+        // Set to false so the items below the toolbar are visible.
+        mRecyclerView.setClipToPadding(false);
         // Check if a layout manager has already been set via XML
-        boolean isLayoutMangerSet = getLayoutManager() != null;
-        if (!isLayoutMangerSet && carUiRecyclerViewLayout
-                == CarUiRecyclerView.CarUiRecyclerViewLayout.LINEAR) {
-            setLayoutManager(new LinearLayoutManager(getContext()) {
-                @Override
-                public void onLayoutCompleted(RecyclerView.State state) {
-                    super.onLayoutCompleted(state);
-                    // Iterate through a copied set instead of the original set because the original
-                    // set might be modified during iteration.
-                    Set<Runnable> onLayoutCompletedListeners =
-                        new HashSet<>(mOnLayoutCompletedListeners);
-                    for (Runnable runnable : onLayoutCompletedListeners) {
-                        runnable.run();
-                    }
-                }
-            });
-        } else if (!isLayoutMangerSet && carUiRecyclerViewLayout
-                == CarUiRecyclerView.CarUiRecyclerViewLayout.GRID) {
-            setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns) {
-                @Override
-                public void onLayoutCompleted(RecyclerView.State state) {
-                    super.onLayoutCompleted(state);
-                    // Iterate through a copied set instead of the original set because the original
-                    // set might be modified during iteration.
-                    Set<Runnable> onLayoutCompletedListeners =
-                        new HashSet<>(mOnLayoutCompletedListeners);
-                    for (Runnable runnable : onLayoutCompletedListeners) {
-                        runnable.run();
-                    }
-                }
-            });
+        String layoutManagerInXml = a.getString(R.styleable.CarUiRecyclerView_layoutManager);
+        if (!TextUtils.isEmpty(layoutManagerInXml)) {
+            createLayoutManager(context, layoutManagerInXml, attrs, defStyleAttr, 0);
+        } else if (carUiRecyclerViewLayout == CarUiRecyclerViewLayout.GRID) {
+            setLayoutManager(new GridLayoutManager(getContext(), mNumOfColumns));
+        } else {
+            // carUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR
+            // Also the default case
+            setLayoutManager(new LinearLayoutManager(getContext()));
         }
-        addOnScrollListener(mOnScrollListener);
-
-        mSize = a.getInt(R.styleable.CarUiRecyclerView_carUiSize, SIZE_LARGE);
 
         a.recycle();
 
@@ -224,25 +251,46 @@
             return;
         }
 
-        mContainer = new FrameLayout(getContext());
-
-        setVerticalScrollBarEnabled(false);
-        setHorizontalScrollBarEnabled(false);
+        mRecyclerView.setVerticalScrollBarEnabled(false);
+        mRecyclerView.setHorizontalScrollBarEnabled(false);
 
         mScrollBarClass = context.getResources().getString(R.string.car_ui_scrollbar_component);
+        createScrollBarFromConfig(context, requireViewByRefId(rootView, R.id.car_ui_scroll_bar));
     }
 
     @Override
     public void setLayoutManager(@Nullable LayoutManager layoutManager) {
-        // Cannot setup item decorations before stylized attributes have been read.
-        if (mIsInitialized) {
-            addItemDecorations(layoutManager);
+        if (layoutManager instanceof GridLayoutManager) {
+            setLayoutStyle(CarUiGridLayoutStyle.from(layoutManager));
+        } else {
+            setLayoutStyle(CarUiLinearLayoutStyle.from(layoutManager));
         }
-        super.setLayoutManager(layoutManager);
+    }
+
+    @Nullable
+    @Override
+    public LayoutManager getLayoutManager() {
+        return mRecyclerView.getLayoutManager();
+    }
+
+    @Override
+    public CarUiLayoutStyle getLayoutStyle() {
+        return mLayoutStyle;
+    }
+
+    @Override
+    public boolean hasFixedSize() {
+        return false;
     }
 
     @Override
     public void setLayoutStyle(CarUiLayoutStyle layoutStyle) {
+        mLayoutStyle = layoutStyle;
+        if (layoutStyle == null) {
+            mRecyclerView.setLayoutManager(null);
+            return;
+        }
+
         LayoutManager layoutManager;
         if (layoutStyle.getLayoutType() == CarUiRecyclerViewLayout.LINEAR) {
             layoutManager = new LinearLayoutManager(getContext(),
@@ -254,7 +302,7 @@
                     // Iterate through a copied set instead of the original set because the original
                     // set might be modified during iteration.
                     Set<Runnable> onLayoutCompletedListeners =
-                        new HashSet<>(mOnLayoutCompletedListeners);
+                            new HashSet<>(mOnLayoutCompletedListeners);
                     for (Runnable runnable : onLayoutCompletedListeners) {
                         runnable.run();
                     }
@@ -271,7 +319,7 @@
                     // Iterate through a copied set instead of the original set because the original
                     // set might be modified during iteration.
                     Set<Runnable> onLayoutCompletedListeners =
-                        new HashSet<>(mOnLayoutCompletedListeners);
+                            new HashSet<>(mOnLayoutCompletedListeners);
                     for (Runnable runnable : onLayoutCompletedListeners) {
                         runnable.run();
                     }
@@ -283,7 +331,23 @@
                         ((CarUiGridLayoutStyle) layoutStyle).getSpanSizeLookup());
             }
         }
-        setLayoutManager(layoutManager);
+
+        // Cannot setup item decorations before stylized attributes have been read.
+        if (mIsInitialized) {
+            addItemDecorations(layoutManager);
+        }
+        mRecyclerView.setLayoutManager(layoutManager);
+    }
+
+    @NonNull
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public void invalidateItemDecorations() {
+        mRecyclerView.invalidateItemDecorations();
     }
 
     /**
@@ -295,7 +359,7 @@
     @Override
     public boolean isLayoutCompleted() {
         RecyclerView.Adapter adapter = getAdapter();
-        return adapter != null && adapter.getItemCount() > 0 && !isComputingLayout();
+        return adapter != null && adapter.getItemCount() > 0 && !mRecyclerView.isComputingLayout();
     }
 
     @Override
@@ -313,34 +377,180 @@
     }
 
     @Override
-    public View getContainer() {
-        return mContainer;
+    public ViewHolder findViewHolderForAdapterPosition(int position) {
+        return mRecyclerView.findViewHolderForAdapterPosition(position);
+    }
+
+    @Override
+    public ViewHolder findViewHolderForLayoutPosition(int position) {
+        return mRecyclerView.findViewHolderForLayoutPosition(position);
+    }
+
+    @Override
+    public Adapter<?> getAdapter() {
+        return mRecyclerView.getAdapter();
+    }
+
+    @Override
+    public int getChildLayoutPosition(View child) {
+        return mRecyclerView.getChildLayoutPosition(child);
+    }
+
+    @Override
+    public RecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    @Override
+    public int getRecyclerViewChildCount() {
+        if (mRecyclerView.getLayoutManager() != null) {
+            return mRecyclerView.getLayoutManager().getChildCount();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public View getRecyclerViewChildAt(int index) {
+        if (mRecyclerView.getLayoutManager() != null) {
+            return mRecyclerView.getLayoutManager().getChildAt(index);
+        } else {
+            return null;
+        }
+    }
+
+    private static int toInternalScrollState(int state) {
+        /* default to RecyclerView.SCROLL_STATE_IDLE */
+        int internalState = SCROLL_STATE_IDLE;
+        switch (state) {
+            case RecyclerView.SCROLL_STATE_DRAGGING:
+                internalState = SCROLL_STATE_DRAGGING;
+                break;
+            case RecyclerView.SCROLL_STATE_SETTLING:
+                internalState = SCROLL_STATE_SETTLING;
+                break;
+        }
+        return internalState;
+    }
+
+    @Override
+    public int getScrollState() {
+        return toInternalScrollState(mRecyclerView.getScrollState());
+    }
+
+    @Override
+    public void addOnScrollListener(OnScrollListener scrollListener) {
+        if (mScrollListeners.isEmpty()) {
+            mRecyclerView.addOnScrollListener(mOnScrollListener);
+        }
+        mScrollListeners.add(scrollListener);
+    }
+
+    @Override
+    public void clearOnChildAttachStateChangeListeners() {
+        mRecyclerView.clearOnChildAttachStateChangeListeners();
+    }
+
+    @Override
+    public void clearOnScrollListeners() {
+        mScrollListeners.clear();
+        mRecyclerView.removeOnScrollListener(mOnScrollListener);
+    }
+
+    @Override
+    public void addItemDecoration(
+            @NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.addItemDecoration(decor);
+    }
+
+    @Override
+    public void addItemDecoration(
+            @NonNull RecyclerView.ItemDecoration decor, int index) {
+        mRecyclerView.addItemDecoration(decor, index);
+    }
+
+    @Override
+    public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+        mRecyclerView.addOnChildAttachStateChangeListener(listener);
+    }
+
+    @NonNull
+    @Override
+    public ItemDecoration getItemDecorationAt(int index) {
+        return mRecyclerView.getItemDecorationAt(index);
+    }
+
+    @Override
+    public int getItemDecorationCount() {
+        return mRecyclerView.getItemDecorationCount();
+    }
+
+    @Override
+    public void removeItemDecorationAt(int index) {
+        mRecyclerView.removeItemDecorationAt(index);
+    }
+
+    @Override
+    public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+        mRecyclerView.removeOnChildAttachStateChangeListener(listener);
+    }
+
+    @Override
+    public void removeItemDecoration(
+            @NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.removeItemDecoration(decor);
+    }
+
+    @Override
+    public int findFirstCompletelyVisibleItemPosition() {
+        return ((LinearLayoutManager) Objects.requireNonNull(mRecyclerView.getLayoutManager()))
+                .findFirstCompletelyVisibleItemPosition();
+    }
+
+    @Override
+    public int findFirstVisibleItemPosition() {
+        return ((LinearLayoutManager) Objects.requireNonNull(mRecyclerView.getLayoutManager()))
+                .findFirstVisibleItemPosition();
+    }
+
+    @Override
+    public int findLastCompletelyVisibleItemPosition() {
+        return ((LinearLayoutManager) Objects.requireNonNull(mRecyclerView.getLayoutManager()))
+                .findLastCompletelyVisibleItemPosition();
+    }
+
+    @Override
+    public int findLastVisibleItemPosition() {
+        return ((LinearLayoutManager) Objects.requireNonNull(mRecyclerView.getLayoutManager()))
+                .findLastVisibleItemPosition();
+    }
+
+    @Override
+    public void setSpanSizeLookup(@NonNull SpanSizeLookup spanSizeLookup) {
+        if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) {
+            ((GridLayoutManager) mRecyclerView.getLayoutManager())
+                    .setSpanSizeLookup(spanSizeLookup);
+        }
     }
 
     // This method should not be invoked before item decorations are initialized by the #init()
     // method.
     private void addItemDecorations(LayoutManager layoutManager) {
         // remove existing Item decorations.
-        removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
-        removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
-        removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
-        removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
-        removeItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
-        removeItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
+        mRecyclerView.removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
+        mRecyclerView.removeItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
 
         if (layoutManager instanceof GridLayoutManager) {
             if (mEnableDividers) {
-                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationGrid));
+                mRecyclerView.addItemDecoration(
+                        Objects.requireNonNull(mDividerItemDecorationGrid));
             }
-            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationGrid));
-            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationGrid));
             setNumOfColumns(((GridLayoutManager) layoutManager).getSpanCount());
         } else {
             if (mEnableDividers) {
-                addItemDecoration(Objects.requireNonNull(mDividerItemDecorationLinear));
+                mRecyclerView.addItemDecoration(
+                        Objects.requireNonNull(mDividerItemDecorationLinear));
             }
-            addItemDecoration(Objects.requireNonNull(mTopOffsetItemDecorationLinear));
-            addItemDecoration(Objects.requireNonNull(mBottomOffsetItemDecorationLinear));
         }
     }
 
@@ -349,31 +559,23 @@
      * description so that the {@code RotaryService} will treat it as a scrollable container and
      * initializes this view accordingly.
      */
-    private void initRotaryScroll(@Nullable TypedArray styledAttributes) {
-        boolean rotaryScrollEnabled = styledAttributes != null && styledAttributes.getBoolean(
-                R.styleable.CarUiRecyclerView_rotaryScrollEnabled, /* defValue=*/ false);
+    private void initRotaryScroll(@NonNull ViewGroup recyclerView,
+            boolean rotaryScrollEnabled,
+            int orientation) {
         if (rotaryScrollEnabled) {
-            int orientation = styledAttributes
-                    .getInt(R.styleable.CarUiRecyclerView_android_orientation,
-                            LinearLayout.VERTICAL);
             setRotaryScrollEnabled(
-                    this, /* isVertical= */ orientation == LinearLayout.VERTICAL);
-        } else {
-            CharSequence contentDescription = getContentDescription();
-            rotaryScrollEnabled = contentDescription != null
-                    && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
-                    || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
+                    recyclerView, /* isVertical= */ orientation == LinearLayout.VERTICAL);
         }
 
         // If rotary scrolling is enabled, set a generic motion event listener to convert
         // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView
         // knows how to handle.
-        setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
+        recyclerView.setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
             if (event.getAction() == MotionEvent.ACTION_SCROLL) {
                 if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) {
                     MotionEvent mouseEvent = MotionEvent.obtain(event);
                     mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
-                    CarUiRecyclerViewImpl.super.onGenericMotionEvent(mouseEvent);
+                    recyclerView.onGenericMotionEvent(mouseEvent);
                     return true;
                 }
             }
@@ -382,148 +584,213 @@
 
         // If rotary scrolling is enabled, mark this view as focusable. This view will be focused
         // when no focusable elements are visible.
-        setFocusable(rotaryScrollEnabled);
+        recyclerView.setFocusable(rotaryScrollEnabled);
 
         // Focus this view before descendants so that the RotaryService can focus this view when it
         // wants to.
-        setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        recyclerView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
 
         // Disable the default focus highlight. No highlight should appear when this view is
         // focused.
-        setDefaultFocusHighlightEnabled(false);
+        recyclerView.setDefaultFocusHighlightEnabled(false);
 
         // If rotary scrolling is enabled, set a focus change listener to highlight the scrollbar
         // thumb when this recycler view is focused, i.e. when no focusable descendant is visible.
-        setOnFocusChangeListener(rotaryScrollEnabled ? (v, hasFocus) -> {
+        recyclerView.setOnFocusChangeListener(rotaryScrollEnabled ? (v, hasFocus) -> {
             if (mScrollBar != null) mScrollBar.setHighlightThumb(hasFocus);
         } : null);
 
-        // This view is a rotary container if it's not a scrollable container.
+        // This recyclerView is a rotary container if it's not a scrollable container.
         if (!rotaryScrollEnabled) {
-            super.setContentDescription(ROTARY_CONTAINER);
+            recyclerView.setContentDescription(ROTARY_CONTAINER);
         }
     }
 
     @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        super.onRestoreInstanceState(state);
-
-        // If we're restoring an existing RecyclerView, consider
-        // it as having already scrolled some.
-        mHasScrolled = true;
-    }
-
-    @Override
     public void requestLayout() {
         super.requestLayout();
+        if (mIsInitialized) {
+            mRecyclerView.requestLayout();
+        }
         if (mScrollBar != null) {
             mScrollBar.requestLayout();
         }
     }
 
+    @Override
+    public void removeOnScrollListener(OnScrollListener scrollListener) {
+        mScrollListeners.remove(scrollListener);
+        if (mScrollListeners.isEmpty()) {
+            mRecyclerView.removeOnScrollListener(mOnScrollListener);
+        }
+    }
+
     /**
      * Sets the number of columns in which grid needs to be divided.
      */
     private void setNumOfColumns(int numberOfColumns) {
         mNumOfColumns = numberOfColumns;
-        if (mTopOffsetItemDecorationGrid != null) {
-            mTopOffsetItemDecorationGrid.setNumOfColumns(mNumOfColumns);
-        }
         if (mDividerItemDecorationGrid != null) {
             mDividerItemDecorationGrid.setNumOfColumns(mNumOfColumns);
         }
     }
 
-    /**
-     * Changes the visibility of the entire container. If the container is not present i.e scrollbar
-     * is not visible then the visibility or Recyclerview is changed.
-     */
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-        mContainerVisibility = visibility;
-        if (mContainer != null) {
-            mContainer.setVisibility(visibility);
-        }
-    }
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mCarUxRestrictionsUtil.register(mListener);
-        if (mInstallingExtScrollBar || !mScrollBarEnabled) {
-            return;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mCarUxRestrictionsUtil.unregister(mListener);
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        if (mScrollBarEnabled) {
+            int currentPosition = findFirstVisibleItemPosition();
+            setScrollBarPadding(mScrollBarPaddingTop + top, mScrollBarPaddingBottom + bottom);
+            // Maintain same index position after setting padding
+            scrollToPosition(currentPosition);
         }
-        // When CarUiRV is detached from the current parent and attached to the container with
-        // the scrollBar, onAttachedToWindow() will get called immediately when attaching the
-        // CarUiRV to the container. This flag will help us keep track of this state and avoid
-        // recursion. We also want to reset the state of this flag as soon as the container is
-        // successfully attached to the CarUiRV's original parent.
-        mInstallingExtScrollBar = true;
-        installExternalScrollBar();
-        mInstallingExtScrollBar = false;
+        mRecyclerView.setPadding(0, top, 0, bottom);
+        super.setPadding(left, 0, right, 0);
+    }
+
+    @Override
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        if (mScrollBarEnabled) {
+            int currentPosition = findFirstVisibleItemPosition();
+            setScrollBarPadding(mScrollBarPaddingTop + top, mScrollBarPaddingBottom + bottom);
+            // Maintain same index position after setting padding
+            scrollToPosition(currentPosition);
+        }
+        mRecyclerView.setPaddingRelative(0, top, 0, bottom);
+        super.setPaddingRelative(start, 0, end, 0);
+    }
+
+    @Override
+    public int getPaddingTop() {
+        return mRecyclerView.getPaddingTop();
+    }
+
+    @Override
+    public int getPaddingBottom() {
+        return mRecyclerView.getPaddingBottom();
+    }
+
+    @Override
+    public void smoothScrollBy(int dx, int dy) {
+        mRecyclerView.smoothScrollBy(dx, dy);
+    }
+
+    @Override
+    public void smoothScrollToPosition(int position) {
+        mRecyclerView.smoothScrollToPosition(position);
+    }
+
+    @Override
+    public boolean post(Runnable runnable) {
+        return mRecyclerView.post(runnable);
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mRecyclerView.scrollToPosition(position);
+    }
+
+    @Override
+    public void scrollBy(int x, int y) {
+        mRecyclerView.scrollBy(x, y);
     }
 
     /**
-     * This method will detach the current recycler view from its parent and attach it to the
-     * container which is a LinearLayout. Later the entire container is attached to the parent where
-     * the recycler view was set with the same layout params.
+     * Sets the scrollbar's padding top and bottom. This padding is applied in addition to the
+     * padding of the RecyclerView.
      */
-    private void installExternalScrollBar() {
-        if (mContainer.getParent() != null) {
-            // We've already installed the parent container.
-            // onAttachToWindow() can be called multiple times, but on the second time
-            // we will crash if we try to add mContainer as a child of a view again while
-            // it already has a parent.
-            return;
+    private void setScrollBarPadding(int paddingTop, int paddingBottom) {
+        if (mScrollBarEnabled && mScrollBar != null) {
+            mScrollBar.setPadding(paddingTop, paddingBottom);
         }
-
-        mContainer.removeAllViews();
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-
-        switch (mSize) {
-            case SIZE_SMALL:
-                // Small layout is rendered without scrollbar
-                return;
-            case SIZE_MEDIUM:
-                inflater.inflate(R.layout.car_ui_recycler_view_medium, mContainer, true);
-                break;
-            case SIZE_LARGE:
-            default:
-                inflater.inflate(R.layout.car_ui_recycler_view, mContainer, true);
-        }
-
-        mContainer.setVisibility(mContainerVisibility);
-
-        if (mContainerPadding != null) {
-            mContainer.setPadding(mContainerPadding.left, mContainerPadding.top,
-                    mContainerPadding.right, mContainerPadding.bottom);
-        } else if (mContainerPaddingRelative != null) {
-            mContainer.setPaddingRelative(mContainerPaddingRelative.left,
-                    mContainerPaddingRelative.top, mContainerPaddingRelative.right,
-                    mContainerPaddingRelative.bottom);
-        } else {
-            mContainer.setPadding(getPaddingLeft(), /* top= */ 0,
-                    getPaddingRight(), /* bottom= */ 0);
-            setPadding(/* left= */ 0, getPaddingTop(),
-                    /* right= */ 0, getPaddingBottom());
-        }
-
-        mContainer.setLayoutParams(getLayoutParams());
-        ViewGroup parent = (ViewGroup) getParent();
-        int index = parent.indexOfChild(this);
-        parent.removeViewInLayout(this);
-
-        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-        ((CarUiRecyclerViewContainer) requireViewByRefId(mContainer, R.id.car_ui_recycler_view))
-                .addRecyclerView(this, params);
-        parent.addView(mContainer, index);
-
-        createScrollBarFromConfig(requireViewByRefId(mContainer, R.id.car_ui_scroll_bar));
     }
 
-    private void createScrollBarFromConfig(@NonNull View scrollView) {
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+        boolean rotaryScrollEnabled = contentDescription != null
+                && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+                || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
+        int orientation = getLayoutStyle() == null ? LinearLayout.VERTICAL
+                : getLayoutStyle().getOrientation();
+        initRotaryScroll(mRecyclerView, rotaryScrollEnabled, orientation);
+        // Only change this view's content description when not related to rotary scroll. Don't
+        // change its content description when related to rotary scroll, because the content
+        // description should be set on its inner recyclerview in this case.
+        if (!rotaryScrollEnabled) {
+            super.setContentDescription(contentDescription);
+        }
+    }
+
+    @Override
+    public void setAdapter(@Nullable Adapter<?> adapter) {
+        if (mScrollBar != null) {
+            // Make sure this is called before super so that scrollbar can get a reference to
+            // the adapter using RecyclerView#getAdapter()
+            mScrollBar.adapterChanged(adapter);
+        }
+        if (mAdapter instanceof OnAttachListener) {
+            ((OnAttachListener) mAdapter).onDetachedFromCarUiRecyclerView(this);
+        }
+        mAdapter = adapter;
+        mRecyclerView.setAdapter(adapter);
+        if (adapter instanceof OnAttachListener) {
+            ((OnAttachListener) adapter).onAttachedToCarUiRecyclerView(this);
+        }
+    }
+
+    @Override
+    public void setItemAnimator(ItemAnimator itemAnimator) {
+        mRecyclerView.setItemAnimator(itemAnimator);
+    }
+
+    @Override
+    public void setHasFixedSize(boolean hasFixedSize) {
+        mRecyclerView.setHasFixedSize(hasFixedSize);
+    }
+
+    @Override
+    public void setOnFlingListener(OnFlingListener listener) {
+        mRecyclerView.setOnFlingListener(listener);
+    }
+
+    private OrientationHelper createOrientationHelper() {
+        if (mLayoutStyle.getOrientation() == CarUiLayoutStyle.VERTICAL) {
+            return OrientationHelper.createVerticalHelper(mRecyclerView.getLayoutManager());
+        } else {
+            return OrientationHelper.createHorizontalHelper(mRecyclerView.getLayoutManager());
+        }
+    }
+
+    @Override
+    public int getEndAfterPadding() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getEndAfterPadding();
+    }
+
+    @Override
+    public int getStartAfterPadding() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getStartAfterPadding();
+    }
+
+    @Override
+    public int getTotalSpace() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getTotalSpace();
+    }
+
+    private void createScrollBarFromConfig(Context context, View scrollView) {
         Class<?> cls;
         try {
             cls = !TextUtils.isEmpty(mScrollBarClass)
@@ -542,124 +809,10 @@
                     + mScrollBarClass, e);
         }
 
-        mScrollBar.initialize(this, scrollView);
+        mScrollBar.initialize(context, mRecyclerView, scrollView);
 
-        setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
-    }
-
-    @Override
-    public void setAlpha(float value) {
-        if (mScrollBarEnabled) {
-            mContainer.setAlpha(value);
-        } else {
-            super.setAlpha(value);
-        }
-    }
-
-    @Override
-    public ViewPropertyAnimator animate() {
-        return mScrollBarEnabled ? mContainer.animate() : super.animate();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mCarUxRestrictionsUtil.unregister(mListener);
-    }
-
-    @Override
-    public int getPaddingLeft() {
-        if (mContainerPadding != null) {
-            return mContainerPadding.left;
-        }
-
-        return super.getPaddingLeft();
-    }
-
-    @Override
-    public int getPaddingRight() {
-        if (mContainerPadding != null) {
-            return mContainerPadding.right;
-        }
-
-        return super.getPaddingRight();
-    }
-
-    @Override
-    public void setPadding(int left, int top, int right, int bottom) {
-        mContainerPaddingRelative = null;
-        if (mScrollBarEnabled) {
-            boolean isAtStart = (mScrollBar != null && mScrollBar.isAtStart());
-            super.setPadding(0, top, 0, bottom);
-            if (!mHasScrolled || isAtStart) {
-                // If we haven't scrolled, and thus are still at the top of the screen,
-                // we should stay scrolled to the top after applying padding. Without this
-                // scroll, the padding will start scrolled offscreen. We need the padding
-                // to be onscreen to shift the content into a good visible range.
-                scrollToPosition(0);
-            }
-            mContainerPadding = new Rect(left, 0, right, 0);
-            if (mContainer != null) {
-                mContainer.setPadding(left, 0, right, 0);
-            }
-            setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
-        } else {
-            super.setPadding(left, top, right, bottom);
-        }
-    }
-
-    @Override
-    public void setPaddingRelative(int start, int top, int end, int bottom) {
-        mContainerPadding = null;
-        if (mScrollBarEnabled) {
-            super.setPaddingRelative(0, top, 0, bottom);
-            if (!mHasScrolled) {
-                // If we haven't scrolled, and thus are still at the top of the screen,
-                // we should stay scrolled to the top after applying padding. Without this
-                // scroll, the padding will start scrolled offscreen. We need the padding
-                // to be onscreen to shift the content into a good visible range.
-                scrollToPosition(0);
-            }
-            mContainerPaddingRelative = new Rect(start, 0, end, 0);
-            if (mContainer != null) {
-                mContainer.setPaddingRelative(start, 0, end, 0);
-            }
-            setScrollBarPadding(mScrollBarPaddingTop, mScrollBarPaddingBottom);
-        } else {
-            super.setPaddingRelative(start, top, end, bottom);
-        }
-    }
-
-    /**
-     * Sets the scrollbar's padding top and bottom. This padding is applied in addition to the
-     * padding of the RecyclerView.
-     */
-    private void setScrollBarPadding(int paddingTop, int paddingBottom) {
-        if (mScrollBarEnabled) {
-            mScrollBarPaddingTop = paddingTop;
-            mScrollBarPaddingBottom = paddingBottom;
-
-            if (mScrollBar != null) {
-                mScrollBar.setPadding(paddingTop + getPaddingTop(),
-                        paddingBottom + getPaddingBottom());
-            }
-        }
-    }
-
-    @Override
-    public void setContentDescription(CharSequence contentDescription) {
-        super.setContentDescription(contentDescription);
-        initRotaryScroll(/* styledAttributes= */ null);
-    }
-
-    @Override
-    public void setAdapter(@Nullable Adapter adapter) {
-        if (mScrollBar != null) {
-            // Make sure this is called before super so that scrollbar can get a reference to
-            // the adapter using RecyclerView#getAdapter()
-            mScrollBar.adapterChanged(adapter);
-        }
-        super.setAdapter(adapter);
+        setScrollBarPadding(mScrollBarPaddingTop + getPaddingTop(),
+                mScrollBarPaddingBottom + getPaddingBottom());
     }
 
     private class UxRestrictionChangedListener implements
@@ -667,14 +820,14 @@
 
         @Override
         public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
-            Adapter<?> adapter = getAdapter();
+            Adapter<?> adapter = mRecyclerView.getAdapter();
             // If the adapter does not implement ItemCap, then the max items on it cannot be
             // updated.
-            if (!(adapter instanceof CarUiRecyclerView.ItemCap)) {
+            if (!(adapter instanceof ItemCap)) {
                 return;
             }
 
-            int maxItems = CarUiRecyclerView.ItemCap.UNLIMITED;
+            int maxItems = ItemCap.UNLIMITED;
             if ((carUxRestrictions.getActiveRestrictions()
                     & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT)
                     != 0) {
@@ -682,7 +835,7 @@
             }
 
             int originalCount = adapter.getItemCount();
-            ((CarUiRecyclerView.ItemCap) adapter).setMaxItems(maxItems);
+            ((ItemCap) adapter).setMaxItems(maxItems);
             int newCount = adapter.getItemCount();
 
             if (newCount == originalCount) {
@@ -696,4 +849,75 @@
             }
         }
     }
+
+    /**
+     * Instantiate and set a LayoutManager, if specified in the attributes. exact copy of
+     * {@link Recyclerview#createLayoutManager(Context, String, int, int)}
+     */
+    private void createLayoutManager(Context context, String className, AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        if (className != null) {
+            className = className.trim();
+            if (!className.isEmpty()) {
+                className = getFullClassName(context, className);
+                try {
+                    ClassLoader classLoader;
+                    if (isInEditMode()) {
+                        // layoutlib cannot handle simple class loaders.
+                        classLoader = this.getClass().getClassLoader();
+                    } else {
+                        classLoader = context.getClassLoader();
+                    }
+                    Class<? extends LayoutManager> layoutManagerClass =
+                            Class.forName(className, false, classLoader)
+                                    .asSubclass(LayoutManager.class);
+                    Constructor<? extends LayoutManager> constructor;
+                    Object[] constructorArgs = null;
+                    try {
+                        constructor = layoutManagerClass
+                                .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
+                        constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
+                    } catch (NoSuchMethodException e) {
+                        try {
+                            constructor = layoutManagerClass.getConstructor();
+                        } catch (NoSuchMethodException e1) {
+                            e1.initCause(e);
+                            throw new IllegalStateException(attrs.getPositionDescription()
+                                    + ": Error creating LayoutManager " + className, e1);
+                        }
+                    }
+                    constructor.setAccessible(true);
+                    setLayoutManager(constructor.newInstance(constructorArgs));
+                } catch (ClassNotFoundException e) {
+                    throw new IllegalStateException(attrs.getPositionDescription()
+                            + ": Unable to find LayoutManager " + className, e);
+                } catch (InvocationTargetException e) {
+                    throw new IllegalStateException(attrs.getPositionDescription()
+                            + ": Could not instantiate the LayoutManager: " + className, e);
+                } catch (InstantiationException e) {
+                    throw new IllegalStateException(attrs.getPositionDescription()
+                            + ": Could not instantiate the LayoutManager: " + className, e);
+                } catch (IllegalAccessException e) {
+                    throw new IllegalStateException(attrs.getPositionDescription()
+                            + ": Cannot access non-public constructor " + className, e);
+                } catch (ClassCastException e) {
+                    throw new IllegalStateException(attrs.getPositionDescription()
+                            + ": Class is not a LayoutManager " + className, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * exact copy of {@link RecyclerView#getFullClassName(Context, String)}
+     */
+    private String getFullClassName(Context context, String className) {
+        if (className.charAt(0) == '.') {
+            return context.getPackageName() + className;
+        }
+        if (className.contains(".")) {
+            return className;
+        }
+        return RecyclerView.class.getPackage().getName() + '.' + className;
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
index 1a97b97..b7ada4e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ContentLimitingAdapter.java
@@ -36,6 +36,7 @@
     private static final int SCROLLING_LIMITED_MESSAGE_VIEW_TYPE = Integer.MAX_VALUE;
 
     private Integer mScrollingLimitedMessageResId;
+    @NonNull
     private RangeFilter mRangeFilter = new PassThroughFilter();
     private RecyclerView mRecyclerView;
     private boolean mIsLimiting = false;
@@ -247,23 +248,19 @@
 
     @Override
     public void setMaxItems(int maxItems) {
+        // remove the original filter first.
+        mRangeFilter.removeFilter();
         if (maxItems >= 0) {
-            if (mRangeFilter != null && mIsLimiting) {
-                Log.w(TAG, "A new filter range received before parked");
-                // remove the original filter first.
-                mRangeFilter.removeFilter();
-            }
             mIsLimiting = true;
             mRangeFilter = new RangeFilterImpl(this, maxItems);
             mRangeFilter.recompute(getUnrestrictedItemCount(), computeAnchorIndexWhenRestricting());
             mRangeFilter.applyFilter();
             autoScrollWhenRestricted();
         } else {
-            mRangeFilter.removeFilter();
-
             mIsLimiting = false;
             mRangeFilter = new PassThroughFilter();
             mRangeFilter.recompute(getUnrestrictedItemCount(), 0);
+            mRangeFilter.applyFilter();
         }
     }
 
@@ -298,10 +295,10 @@
     }
 
     /**
-     * Updates the changes from underlying data along with a new anchor.
+     * Updates the changes from underlying data along with a new pivot.
      */
-    public void updateUnderlyingDataChanged(int unrestrictedCount, int newAnchorIndex) {
-        mRangeFilter.recompute(unrestrictedCount, newAnchorIndex);
+    public void updateUnderlyingDataChanged(int unrestrictedCount, int newPivotIndex) {
+        mRangeFilter.recompute(unrestrictedCount, newPivotIndex);
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
index 698f8ae..c7cc247 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -15,11 +15,14 @@
  */
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 
+import android.annotation.TargetApi;
+import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
@@ -43,63 +46,93 @@
 /**
  * The default scroll bar widget for the {@link CarUiRecyclerView}.
  *
- * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic
+ * <p>Inspired by {@code androidx.car.widget.PagedListView}. Most pagination and scrolling logic
  * has been ported from the PLV with minor updates.
  */
+@TargetApi(MIN_TARGET_API)
 class DefaultScrollBar implements ScrollBar {
-
+    private final SparseArray<Integer> mChildHeightByAdapterPosition = new SparseArray();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+    private final RecyclerView.AdapterDataObserver mAdapterChangeObserver =
+            new RecyclerView.AdapterDataObserver() {
+                @Override
+                public void onChanged() {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+                @Override
+                public void onItemRangeInserted(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+                @Override
+                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+                @Override
+                public void onItemRangeRemoved(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                    updatePaginationButtons();
+                }
+            };
 
     private float mButtonDisabledAlpha;
     private CarUiSnapHelper mSnapHelper;
-
     private View mScrollView;
     private View mScrollTrack;
     private View mScrollThumb;
     private View mUpButton;
     private View mDownButton;
     private int mScrollbarThumbMinHeight;
-
+    private Context mContext;
     private RecyclerView mRecyclerView;
-
-    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
     private OrientationHelper mOrientationHelper;
-
     private OnContinuousScrollListener mPageUpOnContinuousScrollListener;
     private OnContinuousScrollListener mPageDownOnContinuousScrollListener;
 
     @Override
-    public void initialize(RecyclerView rv, View scrollView) {
+    public void initialize(Context context, RecyclerView rv, View scrollView) {
+        mContext = context;
         mRecyclerView = rv;
 
         mScrollView = scrollView;
 
-        Resources res = rv.getContext().getResources();
+        Resources res = context.getResources();
 
         mButtonDisabledAlpha = CarUiUtils.getFloat(res, R.dimen.car_ui_button_disabled_alpha);
-        mScrollbarThumbMinHeight = (int) rv.getContext().getResources()
-                .getDimension(R.dimen.car_ui_scrollbar_min_thumb_height);
+        mScrollbarThumbMinHeight =
+                res.getDimensionPixelSize(R.dimen.car_ui_scrollbar_min_thumb_height);
 
         mUpButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_up);
         View.OnClickListener paginateUpButtonOnClickListener = v -> pageUp();
         mUpButton.setOnClickListener(paginateUpButtonOnClickListener);
-        mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+        mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(context,
                 paginateUpButtonOnClickListener);
         mUpButton.setOnTouchListener(mPageUpOnContinuousScrollListener);
 
         mDownButton = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_page_down);
         View.OnClickListener paginateDownButtonOnClickListener = v -> pageDown();
         mDownButton.setOnClickListener(paginateDownButtonOnClickListener);
-        mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(rv.getContext(),
+        mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(context,
                 paginateDownButtonOnClickListener);
         mDownButton.setOnTouchListener(mPageDownOnContinuousScrollListener);
 
         mScrollTrack = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_track);
         mScrollThumb = requireViewByRefId(mScrollView, R.id.car_ui_scrollbar_thumb);
 
-        mSnapHelper = new CarUiSnapHelper(rv.getContext());
+        mSnapHelper = new CarUiSnapHelper(context);
         getRecyclerView().setOnFlingListener(null);
         mSnapHelper.attachToRecyclerView(getRecyclerView());
 
@@ -109,7 +142,7 @@
 
         getRecyclerView().addOnScrollListener(mRecyclerViewOnScrollListener);
 
-        mScrollView.setVisibility(View.INVISIBLE);
+        mScrollView.setVisibility(View.GONE);
         mScrollView.addOnLayoutChangeListener(
                 (View v,
                         int left,
@@ -120,6 +153,10 @@
                         int oldTop,
                         int oldRight,
                         int oldBottom) -> mHandler.post(this::updatePaginationButtons));
+
+        if (mRecyclerView.getAdapter() != null) {
+            adapterChanged(mRecyclerView.getAdapter());
+        }
     }
 
     public RecyclerView getRecyclerView() {
@@ -143,13 +180,16 @@
             if (mRecyclerView.getAdapter() != null) {
                 mRecyclerView.getAdapter().unregisterAdapterDataObserver(mAdapterChangeObserver);
             }
+        } catch (IllegalStateException e) {
+            // adapter was not registered and we're trying to unregister again. ignore.
+        }
+
+        try {
             if (adapter != null) {
                 adapter.registerAdapterDataObserver(mAdapterChangeObserver);
             }
         } catch (IllegalStateException e) {
-            // adapter is already registered. and we're trying to register again.
-            // or adapter was not registered and we're trying to unregister again.
-            // ignore.
+            // adapter is already registered. and we're trying to register again. ignore.
         }
     }
 
@@ -290,35 +330,6 @@
                     cacheChildrenHeight(recyclerView.getLayoutManager());
                 }
             };
-    private final SparseArray<Integer> mChildHeightByAdapterPosition = new SparseArray();
-
-    private final RecyclerView.AdapterDataObserver mAdapterChangeObserver =
-            new RecyclerView.AdapterDataObserver() {
-                @Override
-                public void onChanged() {
-                    clearCachedHeights();
-                }
-                @Override
-                public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
-                    clearCachedHeights();
-                }
-                @Override
-                public void onItemRangeChanged(int positionStart, int itemCount) {
-                    clearCachedHeights();
-                }
-                @Override
-                public void onItemRangeInserted(int positionStart, int itemCount) {
-                    clearCachedHeights();
-                }
-                @Override
-                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-                    clearCachedHeights();
-                }
-                @Override
-                public void onItemRangeRemoved(int positionStart, int itemCount) {
-                    clearCachedHeights();
-                }
-            };
 
     private void clearCachedHeights() {
         mChildHeightByAdapterPosition.clear();
@@ -494,7 +505,7 @@
         RecyclerView.LayoutManager layoutManager = getLayoutManager();
 
         if (layoutManager == null) {
-            mScrollView.setVisibility(View.INVISIBLE);
+            mScrollView.setVisibility(View.GONE);
             return;
         }
 
@@ -505,13 +516,16 @@
         setUpEnabled(!isAtStart);
         setDownEnabled(!isAtEnd);
 
+        boolean isScrollViewVisiblePreUpdate = mScrollView.getVisibility() == View.VISIBLE;
+        boolean isLayoutRequired = false;
+
         if ((isAtStart && isAtEnd) || layoutManager.getItemCount() == 0) {
-            mScrollView.setVisibility(View.INVISIBLE);
+            mScrollView.setVisibility(View.GONE);
         } else {
             OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
             int screenSize = orientationHelper.getTotalSpace();
-            int touchTargetSize = (int) getRecyclerView().getContext().getResources()
-                    .getDimension(R.dimen.car_ui_touch_target_size);
+            int touchTargetSize = mContext.getResources()
+                    .getDimensionPixelSize(R.dimen.car_ui_touch_target_size);
             ViewGroup.MarginLayoutParams upButtonLayoutParam =
                     (ViewGroup.MarginLayoutParams) mUpButton.getLayoutParams();
             int upButtonMargin = upButtonLayoutParam.topMargin
@@ -522,7 +536,10 @@
                     + downButtonLayoutParam.bottomMargin;
             int margin = upButtonMargin + downButtonMargin;
             if (screenSize < 2 * touchTargetSize + margin) {
-                mScrollView.setVisibility(View.INVISIBLE);
+                if (isScrollViewVisiblePreUpdate) {
+                    isLayoutRequired = true;
+                }
+                mScrollView.setVisibility(View.GONE);
             } else {
                 ViewGroup.MarginLayoutParams trackLayoutParam =
                         (ViewGroup.MarginLayoutParams) mScrollTrack.getLayoutParams();
@@ -540,6 +557,10 @@
                     mScrollTrack.setVisibility(View.VISIBLE);
                     mScrollThumb.setVisibility(View.VISIBLE);
                 }
+
+                if (!isScrollViewVisiblePreUpdate) {
+                    isLayoutRequired = true;
+                }
                 mScrollView.setVisibility(View.VISIBLE);
             }
         }
@@ -557,6 +578,13 @@
         }
 
         mScrollView.invalidate();
+        // updatePaginationButtons() is called from onLayoutChangeListener, request layout only when
+        // required to avoid infinite loop.
+        if (isLayoutRequired) {
+            // If currently performing a layout pass, layout update may not be picked up until the
+            // next layout pass. Schedule another layout pass to ensure changes take affect.
+            mScrollView.post(() -> mScrollView.requestLayout());
+        }
     }
 
     /**
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
index 62d9feb..82a26c4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/DelegatingContentLimitingAdapter.java
@@ -16,6 +16,10 @@
 
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.view.ViewGroup;
 
 import androidx.annotation.IdRes;
@@ -31,6 +35,7 @@
  *
  * @param <T> type of the {@link RecyclerView.ViewHolder} objects used by the delegate.
  */
+@TargetApi(MIN_TARGET_API)
 public class DelegatingContentLimitingAdapter<T extends RecyclerView.ViewHolder>
         extends ContentLimitingAdapter<T> {
     private static final int SCROLLING_LIMITED_MESSAGE_VIEW_TYPE = Integer.MAX_VALUE;
@@ -126,6 +131,7 @@
 
     private class Observer extends RecyclerView.AdapterDataObserver {
 
+        @SuppressLint("NotifyDataSetChanged")
         @Override
         public void onChanged() {
             DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
@@ -155,6 +161,7 @@
                     .notifyItemRangeRemoved(positionStart, itemCount);
         }
 
+        @SuppressLint("NotifyDataSetChanged")
         @Override
         public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
             DelegatingContentLimitingAdapter.this.notifyDataSetChanged();
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
index 0a60090..d15739e 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/FastScroller.java
@@ -18,6 +18,7 @@
 
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.SuppressLint;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -34,6 +35,7 @@
  *     <li>User can click anywhere on the track and thumb will scroll to that position.</li>
  * </ul>
  */
+@SuppressLint("ClickableViewAccessibility")
 class FastScroller implements View.OnTouchListener {
 
     private float mTouchDownY = -1;
@@ -67,7 +69,7 @@
                 break;
             case MotionEvent.ACTION_MOVE:
                 float thumbBottom = mScrollThumb.getY() + mScrollThumb.getHeight();
-                // check if the move coordinates are within the bounds of the thumb. i.e user is
+                // check if the move coordinates are within the bounds of the thumb. i.e. user is
                 // holding and dragging the thumb.
                 if (!(me.getY() + mScrollTrackView.getY() < thumbBottom
                         && me.getY() + mScrollTrackView.getY() > mScrollThumb.getY())) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
index 5b5d616..3b705e2 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/OnContinuousScrollListener.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.recyclerview;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
@@ -33,6 +34,7 @@
  * the provided clickListener. The first callback is fired after the initial Delay, and subsequent
  * ones after the defined interval.
  */
+@SuppressLint("ClickableViewAccessibility")
 public class OnContinuousScrollListener implements OnTouchListener {
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ProxyRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ProxyRecyclerView.java
new file mode 100644
index 0000000..769ef01
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ProxyRecyclerView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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.ui.recyclerview;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * A proxy class that passes through all the calls to the CarUiRecyclerView supplied in
+ * {@code #ProxyRecyclerView(Context, com.android.car.ui.CarUiRecyclerView)}
+ */
+public final class ProxyRecyclerView extends RecyclerView {
+
+    @NonNull
+    private CarUiRecyclerView mTarget;
+
+    private ProxyRecyclerView(
+            @NonNull Context context) {
+        super(context);
+    }
+
+    private ProxyRecyclerView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private ProxyRecyclerView(@NonNull Context context,
+            @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public ProxyRecyclerView(@NonNull Context context, @NonNull CarUiRecyclerView target) {
+        super(context);
+        mTarget = target;
+    }
+
+    @Override
+    public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
+        mTarget.setAdapter(adapter);
+    }
+
+    @Nullable
+    @Override
+    public Adapter getAdapter() {
+        return mTarget.getAdapter();
+    }
+
+    @Override
+    public void addItemDecoration(
+            @NonNull RecyclerView.ItemDecoration decor) {
+        mTarget.addItemDecoration(decor);
+    }
+
+    @Override
+    public void invalidateItemDecorations() {
+        mTarget.invalidateItemDecorations();
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mTarget.scrollToPosition(position);
+    }
+
+    @Override
+    public void focusableViewAvailable(View v) {
+        mTarget.focusableViewAvailable(v);
+    }
+}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterAdapterV1.java
index f4a3292..c099053 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterAdapterV1.java
@@ -15,8 +15,10 @@
  */
 package com.android.car.ui.recyclerview;
 
+import android.content.Context;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -25,10 +27,10 @@
 import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterDataObserverOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterDataObserverOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -39,26 +41,24 @@
 public final class RecyclerViewAdapterAdapterV1
         implements AdapterOEMV1<RecyclerViewAdapterAdapterV1.ViewHolderAdapterV1> {
 
-    @Nullable
-    private RecyclerView mRecyclerView;
-
     @NonNull
     private final Adapter mAdapter;
-
+    @NonNull
+    private final Context mAppContext;
     @NonNull
     private List<AdapterDataObserverOEMV1> mAdapterDataObservers = new ArrayList<>();
 
     private AdapterDataObserver mAdapterDataObserver = new AdapterDataObserver() {
         @Override
         public void onChanged() {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onChanged();
             }
         }
 
         @Override
         public void onItemRangeChanged(int positionStart, int itemCount) {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onItemRangeChanged(positionStart, itemCount);
             }
         }
@@ -66,28 +66,28 @@
         @Override
         public void onItemRangeChanged(int positionStart, int itemCount,
                 @Nullable Object payload) {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onItemRangeChanged(positionStart, itemCount, payload);
             }
         }
 
         @Override
         public void onItemRangeInserted(int positionStart, int itemCount) {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onItemRangeInserted(positionStart, itemCount);
             }
         }
 
         @Override
         public void onItemRangeRemoved(int positionStart, int itemCount) {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onItemRangeRemoved(positionStart, itemCount);
             }
         }
 
         @Override
         public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 for (int i = 0; i < itemCount; i++) {
                     observer.onItemMoved(fromPosition + i, toPosition + i);
                 }
@@ -96,13 +96,15 @@
 
         @Override
         public void onStateRestorationPolicyChanged() {
-            for (AdapterDataObserverOEMV1 observer: mAdapterDataObservers) {
+            for (AdapterDataObserverOEMV1 observer : mAdapterDataObservers) {
                 observer.onStateRestorationPolicyChanged();
             }
         }
     };
 
-    public RecyclerViewAdapterAdapterV1(@NonNull RecyclerView.Adapter adapter) {
+    public RecyclerViewAdapterAdapterV1(@NonNull Context appContext,
+            @NonNull RecyclerView.Adapter<?> adapter) {
+        mAppContext = appContext;
         mAdapter = adapter;
     }
 
@@ -141,10 +143,9 @@
 
     @Override
     public void onAttachedToRecyclerView(RecyclerViewOEMV1 recyclerView) {
-        if (mRecyclerView != null) {
-            mAdapter.onAttachedToRecyclerView(mRecyclerView);
-            mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
-        }
+        // TODO: can we return something other than null here?
+        mAdapter.onAttachedToRecyclerView(null);
+        mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
     }
 
     @Override
@@ -154,15 +155,17 @@
 
     @Override
     public ViewHolderAdapterV1 createViewHolder(ViewGroup parent, int viewType) {
-        return new ViewHolderAdapterV1(mAdapter.createViewHolder(parent, viewType));
+        // Return a view created with the app context so that a LayoutInflator created from this
+        // view can find resources as expected.
+        FrameLayout fakeParent = new FrameLayout(mAppContext);
+        return new ViewHolderAdapterV1(mAdapter.createViewHolder(fakeParent, viewType));
     }
 
     @Override
     public void onDetachedFromRecyclerView(RecyclerViewOEMV1 recyclerView) {
-        if (mRecyclerView != null) {
-            mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
-            mAdapter.onDetachedFromRecyclerView(mRecyclerView);
-        }
+        mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
+        // TODO: can we return something other than null here?
+        mAdapter.onDetachedFromRecyclerView(null);
     }
 
     @Override
@@ -207,8 +210,10 @@
     }
 
     @Override
-    public void setRecyclerView(@Nullable View recyclerview) {
-        mRecyclerView = (RecyclerView) recyclerview;
+    public void setMaxItems(int maxItems) {
+        if (mAdapter instanceof CarUiRecyclerView.ItemCap) {
+            ((CarUiRecyclerView.ItemCap) mAdapter).setMaxItems(maxItems);
+        }
     }
 
     static class ViewHolderAdapterV1 implements ViewHolderOEMV1 {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterV1.java
index dc32278..dd7bb86 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/RecyclerViewAdapterV1.java
@@ -15,135 +15,359 @@
  */
 package com.android.car.ui.recyclerview;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
+import androidx.recyclerview.widget.RecyclerView.OnFlingListener;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
-import com.android.car.ui.R;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.LayoutStyleOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.OnScrollListenerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.SpanSizeLookupOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.LayoutStyleOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.OnChildAttachStateChangeListenerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.OnScrollListenerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.preference.PreferenceFragment.AndroidxRecyclerViewProvider;
+import com.android.car.ui.recyclerview.RecyclerViewAdapterAdapterV1.ViewHolderAdapterV1;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * AdapterV1 class for making oem implementation available for UI
- *
+ * <p>
  * For CarUi internal usage only.
  */
-public final class RecyclerViewAdapterV1 extends CarUiRecyclerView
-        implements OnScrollListenerOEMV1 {
+@TargetApi(MIN_TARGET_API)
+public final class RecyclerViewAdapterV1 extends FrameLayout
+        implements CarUiRecyclerView, OnScrollListenerOEMV1, AndroidxRecyclerViewProvider,
+        OnChildAttachStateChangeListenerOEMV1 {
 
     @Nullable
     private RecyclerViewOEMV1 mOEMRecyclerView;
     @Nullable
     private AdapterOEMV1 mOEMAdapter;
+    @Nullable
+    private Adapter<?> mAdapter;
 
-    private List<OnScrollListener> mScrollListeners = new ArrayList<>();
+    @NonNull
+    private final List<OnScrollListener> mScrollListeners = new ArrayList<>();
+    @Nullable
+    private ProxyRecyclerView mRecyclerView;
+    @Nullable
+    private CarUiLayoutStyle mLayoutStyle;
+    private int mHeight;
+    private int mWidth;
+    @NonNull
+    private final List<OnChildAttachStateChangeListener> mChildAttachStateChangeListeners =
+            new ArrayList<>();
 
     public RecyclerViewAdapterV1(@NonNull Context context) {
         this(context, null);
     }
 
     public RecyclerViewAdapterV1(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, R.attr.carUiRecyclerViewStyle);
+        this(context, attrs, 0);
     }
 
     public RecyclerViewAdapterV1(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyle) {
-        super(context, attrs, defStyle);
+        super(context, attrs, defStyle, 0);
+        // Background and padding to be handled by plugin implementation
+        setBackground(null);
+        setPadding(0, 0, 0, 0);
     }
 
     /**
      * Called to pass the oem recyclerview implementation.
-     * @param oemRecyclerView
+     *
+     * @param oemRecyclerView plugin implementation of {@link CarUiRecyclerView}
      */
-    public void setRecyclerViewOEMV1(@NonNull RecyclerViewOEMV1 oemRecyclerView) {
+    public void setRecyclerViewOEMV1(@NonNull RecyclerViewOEMV1 oemRecyclerView,
+            @Nullable RecyclerViewAttributesOEMV1 oemAttrs) {
         mOEMRecyclerView = oemRecyclerView;
 
+        LayoutStyleOEMV1 oemLayoutStyle = mOEMRecyclerView.getLayoutStyle();
+        if (oemLayoutStyle.getLayoutType() == LayoutStyleOEMV1.LAYOUT_TYPE_GRID) {
+            CarUiGridLayoutStyle layoutStyle = new CarUiGridLayoutStyle();
+            layoutStyle.setReverseLayout(oemLayoutStyle.getReverseLayout());
+            layoutStyle.setSpanCount(oemLayoutStyle.getSpanCount());
+            layoutStyle.setOrientation(oemLayoutStyle.getOrientation());
+            layoutStyle.setSize(oemAttrs == null
+                    ? RecyclerViewAttributesOEMV1.SIZE_LARGE : oemAttrs.getSize());
+            mLayoutStyle = layoutStyle;
+        } else {
+            CarUiLinearLayoutStyle layoutStyle = new CarUiLinearLayoutStyle();
+            layoutStyle.setReverseLayout(oemLayoutStyle.getReverseLayout());
+            layoutStyle.setOrientation(oemLayoutStyle.getOrientation());
+            layoutStyle.setSize(oemAttrs == null
+                    ? RecyclerViewAttributesOEMV1.SIZE_LARGE : oemAttrs.getSize());
+            mLayoutStyle = layoutStyle;
+        }
+
+        // Adding this parent so androidx PreferenceFragmentCompat doesn't add the ProxyRecyclerView
+        // to the view hierarchy
+        ViewGroup parent = new FrameLayout(getContext());
+        mRecyclerView = new ProxyRecyclerView(getContext(), this);
+        parent.addView(mRecyclerView);
+
         mOEMRecyclerView.addOnScrollListener(this);
-        super.setLayoutManager(new LinearLayoutManager(getContext()));
-        View oemRV = oemRecyclerView.getView();
-        ViewGroup.LayoutParams params = new MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-        oemRV.setLayoutParams(params);
-        RecyclerView.Adapter adapter = new CustomAdapter(oemRV);
-        super.setAdapter(adapter);
+        mOEMRecyclerView.addOnChildAttachStateChangeListener(this);
+
+        ViewGroup.LayoutParams params = new MarginLayoutParams(mWidth, mHeight);
+        addView(mOEMRecyclerView.getView(), params);
     }
 
     @Override
     public void setLayoutManager(@Nullable LayoutManager layoutManager) {
-        if (layoutManager instanceof LinearLayoutManager) {
-            setLayoutStyle(CarUiLinearLayoutStyle.from(layoutManager));
-        } else {
+        if (layoutManager instanceof GridLayoutManager) {
             setLayoutStyle(CarUiGridLayoutStyle.from(layoutManager));
+        } else {
+            setLayoutStyle(CarUiLinearLayoutStyle.from(layoutManager));
         }
     }
 
     @Override
-    public View getContainer() {
-        return mOEMRecyclerView.getContainer();
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        // Margin should be set by plugin implementation only
+        Rect marginRect = null;
+        if (params instanceof MarginLayoutParams) {
+            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) params;
+            marginRect = new Rect(
+                    Math.max(marginLayoutParams.leftMargin, marginLayoutParams.getMarginStart()),
+                    marginLayoutParams.topMargin, marginLayoutParams.rightMargin,
+                    Math.max(marginLayoutParams.bottomMargin, marginLayoutParams.getMarginEnd()));
+            marginLayoutParams.setMargins(0, 0, 0, 0);
+            marginLayoutParams.setMarginStart(0);
+            marginLayoutParams.setMarginEnd(0);
+        }
+
+        if (mOEMRecyclerView != null) {
+            ViewGroup.LayoutParams pluginLayoutParams =
+                    mOEMRecyclerView.getView().getLayoutParams();
+            // Account for 0dp in ConstraintLayout by swapping to MATCH_PARENT. If intentionally
+            // set to size of 0dp, this FrameLayout will have a 0dp size so MATCH_PARENT will
+            // still result in correct size
+            pluginLayoutParams.width =
+                    params.width == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : params.width;
+            pluginLayoutParams.height =
+                    params.height == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : params.height;
+
+            // Apply requested margins to plugin implementation layout
+            if (marginRect != null && pluginLayoutParams instanceof MarginLayoutParams) {
+                MarginLayoutParams rvMarginParams = (MarginLayoutParams) pluginLayoutParams;
+                rvMarginParams.setMargins(marginRect.left, marginRect.top, marginRect.right,
+                        marginRect.bottom);
+            }
+        }
+
+        mWidth = params.width;
+        mHeight = params.height;
+        super.setLayoutParams(params);
     }
 
     @Override
-    public void setAdapter(RecyclerView.Adapter adapter) {
-        if (mOEMAdapter != null) {
-            mOEMAdapter.setRecyclerView(null);
+    public void setAlpha(float alpha) {
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.setAlpha(alpha);
         }
+    }
 
+    @Override
+    public void setBackground(Drawable background) {
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.getView().setBackground(background);
+        }
+    }
+
+    @Override
+    public void setBackgroundColor(int color) {
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.getView().setBackgroundColor(color);
+        }
+    }
+
+    @Override
+    public void setBackgroundResource(int resid) {
+        if (mOEMRecyclerView != null) {
+            Drawable background = getResources().getDrawable(resid);
+            mOEMRecyclerView.getView().setBackground(background);
+        }
+    }
+
+    @Nullable
+    @Override
+    public RecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    @Override
+    public int getRecyclerViewChildCount() {
+        return mOEMRecyclerView.getRecyclerViewChildCount();
+    }
+
+    @Override
+    public View getRecyclerViewChildAt(int index) {
+        return mOEMRecyclerView.getRecyclerViewChildAt(index);
+    }
+
+    private static int toInternalScrollState(int state) {
+        /* default to RecyclerViewOEMV1.SCROLL_STATE_IDLE */
+        int internalState = SCROLL_STATE_IDLE;
+        switch (state) {
+            case RecyclerViewOEMV1.SCROLL_STATE_DRAGGING:
+                internalState = SCROLL_STATE_DRAGGING;
+                break;
+            case RecyclerViewOEMV1.SCROLL_STATE_SETTLING:
+                internalState = SCROLL_STATE_SETTLING;
+                break;
+        }
+        return internalState;
+    }
+
+    @Override
+    public int getScrollState() {
+        return toInternalScrollState(mOEMRecyclerView.getScrollState());
+    }
+
+    @Override
+    public int getEndAfterPadding() {
+        return mOEMRecyclerView.getEndAfterPadding();
+    }
+
+    @Override
+    public int getStartAfterPadding() {
+        return mOEMRecyclerView.getStartAfterPadding();
+    }
+
+    @Override
+    public int getTotalSpace() {
+        return mOEMRecyclerView.getTotalSpace();
+    }
+
+    @NonNull
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public void invalidateItemDecorations() {
+    }
+
+    @Override
+    public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+    }
+
+    @Override
+    public void removeItemDecorationAt(int index) {
+    }
+
+    @Nullable
+    @Override
+    public RecyclerView.ItemDecoration getItemDecorationAt(int index) {
+        return null;
+    }
+
+    @Override
+    public int getItemDecorationCount() {
+        return 0;
+    }
+
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+        super.setContentDescription(contentDescription);
+        mOEMRecyclerView.setContentDescription(contentDescription);
+    }
+
+    @Override
+    public void setAdapter(RecyclerView.Adapter<?> adapter) {
         if (adapter == null) {
+            mAdapter = null;
+            mOEMAdapter = null;
             mOEMRecyclerView.setAdapter(null);
         } else {
-            mOEMAdapter = new RecyclerViewAdapterAdapterV1(adapter);
+            if (mAdapter instanceof OnAttachListener) {
+                ((OnAttachListener) mAdapter).onDetachedFromCarUiRecyclerView(this);
+            }
+            mAdapter = adapter;
+            mOEMAdapter = new RecyclerViewAdapterAdapterV1(getContext(), adapter);
             mOEMRecyclerView.setAdapter(mOEMAdapter);
-            mOEMAdapter.setRecyclerView(this);
+            if (adapter instanceof OnAttachListener) {
+                ((OnAttachListener) adapter).onAttachedToCarUiRecyclerView(this);
+            }
         }
     }
 
     @Override
-    public void addOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
+    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+    }
+
+    @Override
+    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor, int index) {
+    }
+
+    @Override
+    public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+        mChildAttachStateChangeListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
+        mChildAttachStateChangeListeners.remove(listener);
+    }
+
+    @Override
+    public void clearOnChildAttachStateChangeListeners() {
+        mChildAttachStateChangeListeners.clear();
+        mOEMRecyclerView.clearOnScrollListeners();
+    }
+
+    @Override
+    public void addOnScrollListener(@NonNull OnScrollListener listener) {
         mScrollListeners.add(listener);
     }
 
     @Override
-    public void removeOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
-        if (mScrollListeners != null) {
-            mScrollListeners.remove(listener);
-        }
+    public void removeOnScrollListener(@NonNull OnScrollListener listener) {
+        mScrollListeners.remove(listener);
     }
 
     @Override
     public void clearOnScrollListeners() {
-        if (mScrollListeners != null) {
-            mScrollListeners.clear();
-        }
+        mScrollListeners.clear();
         mOEMRecyclerView.clearOnScrollListeners();
     }
 
     @Override
     public void onScrollStateChanged(@NonNull RecyclerViewOEMV1 recyclerView, int newState) {
-        if (mScrollListeners != null) {
-            for (RecyclerView.OnScrollListener listener: mScrollListeners) {
-                listener.onScrollStateChanged(this, newState);
-            }
+        for (OnScrollListener listener : mScrollListeners) {
+            listener.onScrollStateChanged(this, toInternalScrollState(newState));
         }
     }
 
     @Override
     public void onScrolled(@NonNull RecyclerViewOEMV1 recyclerView, int dx, int dy) {
-        if (mScrollListeners != null) {
-            for (RecyclerView.OnScrollListener listener: mScrollListeners) {
-                listener.onScrolled(this, dx, dy);
-            }
+        for (OnScrollListener listener : mScrollListeners) {
+            listener.onScrolled(this, dx, dy);
         }
     }
 
@@ -164,56 +388,51 @@
 
     @Override
     public ViewHolder findViewHolderForAdapterPosition(int position) {
-        // TODO
+        ViewHolderOEMV1 viewHolder = mOEMRecyclerView.findViewHolderForAdapterPosition(position);
+        if (viewHolder instanceof ViewHolderAdapterV1) {
+            return ((ViewHolderAdapterV1) viewHolder).getViewHolder();
+        }
         return null;
     }
 
     @Override
+    public ViewHolder findViewHolderForLayoutPosition(int position) {
+        ViewHolderOEMV1 viewHolder = mOEMRecyclerView.findViewHolderForLayoutPosition(position);
+        if (viewHolder instanceof ViewHolderAdapterV1) {
+            return ((ViewHolderAdapterV1) viewHolder).getViewHolder();
+        }
+        return null;
+    }
+
+    @Override
+    public Adapter<?> getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public int getChildLayoutPosition(View child) {
+        return mOEMRecyclerView.getChildLayoutPosition(child);
+    }
+
+    @Override
     public void setHasFixedSize(boolean hasFixedSize) {
         mOEMRecyclerView.setHasFixedSize(hasFixedSize);
     }
 
     @Override
+    public void setOnFlingListener(OnFlingListener listener) {
+        // TODO
+    }
+
+    @Override
     public boolean hasFixedSize() {
         return mOEMRecyclerView.hasFixedSize();
     }
 
-    private static class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
-        @NonNull
-        private View mView;
-
-        CustomAdapter(@NonNull View view) {
-            mView = view;
-        }
-
-        @Override
-        public int getItemCount() {
-            return 1;
-        }
-
-        @NonNull
-        @Override
-        public RecyclerView.ViewHolder onCreateViewHolder(
-                @NonNull ViewGroup parent, int viewType) {
-            return new CustomViewHolder(mView);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
-        }
-
-        static class CustomViewHolder extends RecyclerView.ViewHolder {
-            CustomViewHolder(@NonNull View itemView) {
-                super(itemView);
-            }
-        }
-    }
-
     /**
-     * @deprecated LayoutManager will be implemented by OEMs,
-     * use other available APIs to get the required data
      * @return null
+     * @deprecated LayoutManager will be implemented by OEMs, use other available APIs to get the
+     * required data
      */
     @Nullable
     @Override
@@ -223,8 +442,27 @@
     }
 
     @Override
-    public void setLayoutStyle(@Nullable CarUiLayoutStyle layoutStyle) {
-        if (layoutStyle == null) mOEMRecyclerView.setLayoutStyle(null);
+    public CarUiLayoutStyle getLayoutStyle() {
+        return mLayoutStyle;
+    }
+
+    @Override
+    public boolean canScrollHorizontally(int direction) {
+        return mOEMRecyclerView.getView().canScrollHorizontally(direction);
+    }
+
+    @Override
+    public boolean canScrollVertically(int direction) {
+        return mOEMRecyclerView.getView().canScrollVertically(direction);
+    }
+
+    @Override
+    public void setLayoutStyle(CarUiLayoutStyle layoutStyle) {
+        mLayoutStyle = layoutStyle;
+        if (layoutStyle == null) {
+            mOEMRecyclerView.setLayoutStyle(null);
+            return;
+        }
 
         final LayoutStyleOEMV1 oemLayoutStyle = new LayoutStyleOEMV1() {
             @Override
@@ -248,28 +486,142 @@
             }
 
             @Override
-            public SpanSizeLookupOEMV1 getSpanSizeLookup() {
-                if (layoutStyle instanceof CarUiLinearLayoutStyle) return null;
-                return ((CarUiGridLayoutStyle) layoutStyle).getSpanSizeLookup() == null ? null
-                        : position -> ((CarUiGridLayoutStyle) layoutStyle)
-                                .getSpanSizeLookup().getSpanSize(position);
+            public int getSpanSize(int position) {
+                if (layoutStyle instanceof CarUiGridLayoutStyle) {
+                    return ((CarUiGridLayoutStyle) layoutStyle).getSpanSizeLookup()
+                        .getSpanSize(position);
+                }
+                return 1;
             }
         };
-        mOEMRecyclerView.setLayoutStyle(oemLayoutStyle);
+
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.setLayoutStyle(oemLayoutStyle);
+        }
     }
 
     @Override
     public void setPadding(int left, int top, int right, int bottom) {
-        mOEMRecyclerView.setPadding(left, top, right, bottom);
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.getView().setPadding(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public int getPaddingLeft() {
+        if (mOEMRecyclerView != null) {
+            return mOEMRecyclerView.getView().getPaddingLeft();
+        }
+        return super.getPaddingLeft();
+    }
+
+    @Override
+    public int getPaddingTop() {
+        if (mOEMRecyclerView != null) {
+            return mOEMRecyclerView.getView().getPaddingTop();
+        }
+        return super.getPaddingTop();
+    }
+
+    @Override
+    public int getPaddingRight() {
+        if (mOEMRecyclerView != null) {
+            return mOEMRecyclerView.getView().getPaddingRight();
+        }
+        return super.getPaddingRight();
+    }
+
+    @Override
+    public int getPaddingBottom() {
+        if (mOEMRecyclerView != null) {
+            return mOEMRecyclerView.getView().getPaddingBottom();
+        }
+        return super.getPaddingBottom();
+    }
+
+    @Override
+    public int getPaddingStart() {
+        boolean isLtr = getContext().getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_LTR;
+        if (isLtr) {
+            return getPaddingLeft();
+        } else {
+            return getPaddingRight();
+        }
+    }
+
+    @Override
+    public int getPaddingEnd() {
+        boolean isLtr = getContext().getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_LTR;
+        if (isLtr) {
+            return getPaddingRight();
+        } else {
+            return getPaddingLeft();
+        }
     }
 
     @Override
     public void setPaddingRelative(int start, int top, int end, int bottom) {
-        mOEMRecyclerView.setPaddingRelative(start, top, end, bottom);
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.getView().setPaddingRelative(start, top, end, bottom);
+        }
     }
 
     @Override
     public void setClipToPadding(boolean clipToPadding) {
-        mOEMRecyclerView.setClipToPadding(clipToPadding);
+        if (mOEMRecyclerView != null) {
+            mOEMRecyclerView.setClipToPadding(clipToPadding);
+        }
+    }
+
+    @Override
+    public void setItemAnimator(ItemAnimator itemAnimator) {
+    }
+
+    @Override
+    public int findFirstCompletelyVisibleItemPosition() {
+        return mOEMRecyclerView != null ? mOEMRecyclerView
+                .findFirstCompletelyVisibleItemPosition() : 0;
+    }
+
+    @Override
+    public int findFirstVisibleItemPosition() {
+        return mOEMRecyclerView != null ? mOEMRecyclerView
+                .findFirstVisibleItemPosition() : 0;
+    }
+
+    @Override
+    public int findLastCompletelyVisibleItemPosition() {
+        return mOEMRecyclerView != null ? mOEMRecyclerView
+                .findLastCompletelyVisibleItemPosition() : 0;
+    }
+
+    @Override
+    public int findLastVisibleItemPosition() {
+        return mOEMRecyclerView != null ? mOEMRecyclerView
+                .findLastVisibleItemPosition() : 0;
+    }
+
+    @Override
+    public void setSpanSizeLookup(@NonNull SpanSizeLookup spanSizeLookup) {
+        if (mLayoutStyle instanceof CarUiGridLayoutStyle) {
+            ((CarUiGridLayoutStyle) mLayoutStyle).setSpanSizeLookup(spanSizeLookup);
+            setLayoutStyle(mLayoutStyle);
+        }
+    }
+
+    @Override
+    public void onChildViewAttachedToWindow(@NonNull View view) {
+        for (OnChildAttachStateChangeListener listener : mChildAttachStateChangeListeners) {
+            listener.onChildViewAttachedToWindow(view);
+        }
+    }
+
+    @Override
+    public void onChildViewDetachedFromWindow(@NonNull View view) {
+        for (OnChildAttachStateChangeListener listener : mChildAttachStateChangeListeners) {
+            listener.onChildViewDetachedFromWindow(view);
+        }
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java
index 05863d0..b33a8f7 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/ScrollBar.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.recyclerview;
 
+import android.content.Context;
 import android.view.View;
 
 import androidx.recyclerview.widget.RecyclerView;
@@ -28,7 +29,7 @@
      * The concrete class should implement this method to initialize configuration of a scrollbar
      * view.
      */
-    void initialize(RecyclerView recyclerView, View scrollView);
+    void initialize(Context context, RecyclerView recyclerView, View scrollView);
 
     /**
      * Requests layout of the scrollbar. Should be called when there's been a change that will
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java
deleted file mode 100644
index f1085c2..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/grid/GridOffsetItemDecoration.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.ui.recyclerview.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.annotation.NonNull;
-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(
-            @NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent,
-            @NonNull 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(@NonNull Canvas c, @NonNull RecyclerView parent,
-            @NonNull 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-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
deleted file mode 100644
index 62d361e..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/decorations/linear/LinearOffsetItemDecoration.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.ui.recyclerview.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.annotation.NonNull;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.lang.annotation.Retention;
-import java.util.Objects;
-
-/**
- * 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(
-            @NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent,
-            @NonNull 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) Objects.requireNonNull(
-                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(@NonNull Canvas c, @NonNull RecyclerView parent,
-            @NonNull 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;
-        int offsetDrawableRight;
-
-        if (mOffsetPosition == OffsetPosition.START) {
-            parentLeft = parent.getPaddingLeft();
-        } else {
-            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
-            View lastChild = layoutManager.getChildAt(layoutManager.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;
-        int offsetDrawableBottom;
-
-        if (mOffsetPosition == OffsetPosition.START) {
-            parentTop = parent.getPaddingTop();
-        } else {
-            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
-            View lastChild = layoutManager.getChildAt(layoutManager.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-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/OemApiUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/OemApiUtil.java
deleted file mode 100644
index a9c48c3..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/OemApiUtil.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrarysupport;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryVersionProviderOEMV1;
-
-/**
- * Helper class for accessing oem-apis without reflection.
- *
- * Because this class creates adapters, and adapters implement OEM APIs, this class cannot
- * be created with the app's classloader. You must use {@link AdapterClassLoader} to load it
- * so that both the app and shared library's classes can be loaded from this class.
- */
-final class OemApiUtil {
-
-    private static final String TAG = "carui";
-
-    /**
-     * Given a shared library's context, return it's implementation of {@link SharedLibraryFactory}.
-     *
-     * This is done by asking the shared library's {@link SharedLibraryVersionProvider} for a
-     * factory object, and checking if it's instanceof each version of
-     * {@link SharedLibraryFactoryOEMV1}, casting to the correct one when found.
-     *
-     * @param sharedLibraryContext The shared library's context. This context will return
-     *                             the shared library's classloader from
-     *                             {@link Context#getClassLoader()}.
-     * @param appPackageName The package name of the application. This is passed to the shared
-     *                       library so that it can provide unique customizations per-app.
-     * @return A {@link SharedLibraryFactory}
-     */
-    static SharedLibraryFactory getSharedLibraryFactory(
-            Context sharedLibraryContext, String appPackageName) {
-
-        Object oemVersionProvider = null;
-        try {
-            oemVersionProvider = Class
-                    .forName("com.android.car.ui.sharedlibrary.SharedLibraryVersionProviderImpl")
-                    .getDeclaredConstructor()
-                    .newInstance();
-        } catch (ClassNotFoundException e) {
-            Log.i(TAG, "SharedLibraryVersionProviderImpl not found.", e);
-        } catch (ReflectiveOperationException e) {
-            Log.e(TAG, "SharedLibraryVersionProviderImpl could not be instantiated!", e);
-        }
-
-        // Add new version providers in an if-else chain here, in descending version order so
-        // that higher versions are preferred.
-        SharedLibraryVersionProvider versionProvider = null;
-        if (oemVersionProvider instanceof SharedLibraryVersionProviderOEMV1) {
-            versionProvider = new SharedLibraryVersionProviderAdapterV1(
-                    (SharedLibraryVersionProviderOEMV1) oemVersionProvider);
-        } else {
-            Log.e(TAG, "SharedLibraryVersionProviderImpl was not instanceof any known "
-                    + "versions of SharedLibraryVersionProviderOEMV#.");
-        }
-
-        SharedLibraryFactory oemSharedLibraryFactory = null;
-        if (versionProvider != null) {
-            Object factory = versionProvider.getSharedLibraryFactory(
-                    1, sharedLibraryContext, appPackageName);
-            if (factory instanceof SharedLibraryFactoryOEMV1) {
-                oemSharedLibraryFactory = new SharedLibraryFactoryAdapterV1(
-                        (SharedLibraryFactoryOEMV1) factory, sharedLibraryContext);
-            } else {
-                Log.e(TAG, "SharedLibraryVersionProvider found, but did not provide a"
-                        + " factory implementing any known interfaces!");
-            }
-        }
-
-        return oemSharedLibraryFactory;
-    }
-
-    private OemApiUtil() {}
-}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryAdapterV1.java
deleted file mode 100644
index 730d2d8..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactoryAdapterV1.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrarysupport;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.text.SpannableString;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.car.ui.CarUiText;
-import com.android.car.ui.FocusArea;
-import com.android.car.ui.FocusAreaAdapterV1;
-import com.android.car.ui.FocusParkingView;
-import com.android.car.ui.FocusParkingViewAdapterV1;
-import com.android.car.ui.R;
-import com.android.car.ui.appstyledview.AppStyledViewController;
-import com.android.car.ui.appstyledview.AppStyledViewControllerAdapterV1;
-import com.android.car.ui.appstyledview.AppStyledViewControllerImpl;
-import com.android.car.ui.baselayout.Insets;
-import com.android.car.ui.baselayout.InsetsChangedListener;
-import com.android.car.ui.recyclerview.CarUiContentListItem;
-import com.android.car.ui.recyclerview.CarUiHeaderListItem;
-import com.android.car.ui.recyclerview.CarUiLayoutStyle;
-import com.android.car.ui.recyclerview.CarUiListItem;
-import com.android.car.ui.recyclerview.CarUiListItemAdapterAdapterV1;
-import com.android.car.ui.recyclerview.CarUiRecyclerView;
-import com.android.car.ui.recyclerview.CarUiRecyclerView.CarUiRecyclerViewLayout;
-import com.android.car.ui.recyclerview.RecyclerViewAdapterV1;
-import com.android.car.ui.sharedlibrary.oemapis.InsetsOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ContentListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.HeaderListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.LayoutStyleOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.SpanSizeLookupOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
-import com.android.car.ui.toolbar.ToolbarController;
-import com.android.car.ui.toolbar.ToolbarControllerAdapterV1;
-import com.android.car.ui.utils.CarUiUtils;
-import com.android.car.ui.widget.CarUiTextView;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * This class is an wrapper around {@link SharedLibraryFactoryOEMV1} that implements {@link
- * SharedLibraryFactory}, to provide a version-agnostic way of interfacing with the OEM's
- * SharedLibraryFactory.
- */
-public final class SharedLibraryFactoryAdapterV1 implements SharedLibraryFactory {
-    SharedLibraryFactoryOEMV1 mOem;
-    SharedLibraryFactoryStub mFactoryStub;
-
-    public SharedLibraryFactoryAdapterV1(SharedLibraryFactoryOEMV1 oem, Context sharedLibContext) {
-        mOem = oem;
-        mFactoryStub = new SharedLibraryFactoryStub(sharedLibContext);
-
-        mOem.setRotaryFactories(
-                c -> new FocusParkingViewAdapterV1(new FocusParkingView(c)),
-                c -> new FocusAreaAdapterV1(new FocusArea(c)));
-    }
-
-    @Override
-    @Nullable
-    public ToolbarController installBaseLayoutAround(
-            View contentView,
-            InsetsChangedListener insetsChangedListener,
-            boolean toolbarEnabled,
-            boolean fullscreen) {
-
-        if (!mOem.customizesBaseLayout()) {
-            return mFactoryStub.installBaseLayoutAround(contentView,
-                    insetsChangedListener, toolbarEnabled, fullscreen);
-        }
-
-        ToolbarControllerOEMV1 toolbar = mOem.installBaseLayoutAround(contentView,
-                insets -> insetsChangedListener.onCarUiInsetsChanged(adaptInsets(insets)),
-                toolbarEnabled, fullscreen);
-
-        return toolbar != null
-                ? new ToolbarControllerAdapterV1(contentView.getContext(), toolbar)
-                : null;
-    }
-
-    @NonNull
-    @Override
-    public CarUiTextView createTextView(Context context, AttributeSet attrs) {
-        return mFactoryStub.createTextView(context, attrs);
-    }
-
-
-    @Override
-    public AppStyledViewController createAppStyledView(Context activityContext) {
-        AppStyledViewControllerOEMV1 appStyledViewControllerOEMV1 = mOem.createAppStyledView();
-        return appStyledViewControllerOEMV1 == null ? new AppStyledViewControllerImpl(
-                activityContext) : new AppStyledViewControllerAdapterV1(
-                appStyledViewControllerOEMV1);
-    }
-
-    private Insets adaptInsets(InsetsOEMV1 insetsOEM) {
-        return new Insets(insetsOEM.getLeft(), insetsOEM.getTop(),
-                insetsOEM.getRight(), insetsOEM.getBottom());
-    }
-
-    @Override
-    public CarUiRecyclerView createRecyclerView(@NonNull Context context,
-            @Nullable AttributeSet attrs) {
-        RecyclerViewAttributesOEMV1 oemAttrs = from(context, attrs);
-        RecyclerViewOEMV1 oemRecyclerView = mOem.createRecyclerView(context, oemAttrs);
-        if (oemRecyclerView != null) {
-            RecyclerViewAdapterV1 rv = new RecyclerViewAdapterV1(context, attrs);
-            rv.setRecyclerViewOEMV1(oemRecyclerView);
-            return rv;
-        } else {
-            return mFactoryStub.createRecyclerView(context, attrs);
-        }
-    }
-
-    @Override
-    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> createListItemAdapter(
-            List<? extends CarUiListItem> items) {
-        List<ListItemOEMV1> oemItems = CarUiUtils.convertList(items,
-                SharedLibraryFactoryAdapterV1::toOemListItem);
-
-        AdapterOEMV1<? extends ViewHolderOEMV1> oemAdapter = mOem.createListItemAdapter(oemItems);
-        return oemAdapter != null ? new CarUiListItemAdapterAdapterV1(oemAdapter)
-                : mFactoryStub.createListItemAdapter(items);
-    }
-
-    private static RecyclerViewAttributesOEMV1 from(Context context, AttributeSet attrs) {
-        RecyclerViewAttributesOEMV1 oemAttrs = null;
-        if (attrs != null) {
-            TypedArray a = context.obtainStyledAttributes(
-                    attrs,
-                    R.styleable.CarUiRecyclerView,
-                    0,
-                    R.style.Widget_CarUi_CarUiRecyclerView);
-            final int carUiRecyclerViewLayout = a.getInt(
-                    R.styleable.CarUiRecyclerView_layoutStyle,
-                    CarUiRecyclerViewLayout.LINEAR);
-            final int spanCount = a.getInt(
-                    R.styleable.CarUiRecyclerView_numOfColumns, /* defValue= */ 1);
-            final boolean rotaryScrollEnabled = a.getBoolean(
-                    R.styleable.CarUiRecyclerView_rotaryScrollEnabled,
-                    /* defValue=*/ false);
-            final int orientation = a.getInt(
-                    R.styleable.CarUiRecyclerView_android_orientation,
-                    CarUiLayoutStyle.VERTICAL);
-            final boolean reversed = a.getBoolean(
-                    R.styleable.CarUiRecyclerView_reverseLayout, false);
-            final int size = a.getInt(R.styleable.CarUiRecyclerView_carUiSize,
-                    CarUiRecyclerView.SIZE_LARGE);
-            a.recycle();
-
-            final LayoutStyleOEMV1 layoutStyle = new LayoutStyleOEMV1() {
-                @Override
-                public int getSpanCount() {
-                    return spanCount;
-                }
-
-                @Override
-                public int getLayoutType() {
-                    switch (carUiRecyclerViewLayout) {
-                        case CarUiRecyclerViewLayout.GRID:
-                            return LayoutStyleOEMV1.LAYOUT_TYPE_GRID;
-                        case CarUiRecyclerViewLayout.LINEAR:
-                        default:
-                            return LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR;
-                    }
-                }
-
-                @Override
-                public int getOrientation() {
-                    switch (orientation) {
-                        case CarUiLayoutStyle.HORIZONTAL:
-                            return LayoutStyleOEMV1.ORIENTATION_HORIZONTAL;
-                        case CarUiLayoutStyle.VERTICAL:
-                        default:
-                            return LayoutStyleOEMV1.ORIENTATION_VERTICAL;
-                    }
-                }
-
-                @Override
-                public boolean getReverseLayout() {
-                    return reversed;
-                }
-
-                @Override
-                public SpanSizeLookupOEMV1 getSpanSizeLookup() {
-                    // This can be set via setLayoutStyle API later.
-                    return null;
-                }
-            };
-
-            oemAttrs = new RecyclerViewAttributesOEMV1() {
-                @Override
-                public boolean isRotaryScrollEnabled() {
-                    return rotaryScrollEnabled;
-                }
-
-                @Override
-                public int getSize() {
-                    switch (size) {
-                        case CarUiRecyclerView.SIZE_SMALL:
-                            return RecyclerViewAttributesOEMV1.SIZE_SMALL;
-                        case CarUiRecyclerView.SIZE_MEDIUM:
-                            return RecyclerViewAttributesOEMV1.SIZE_MEDIUM;
-                        case CarUiRecyclerView.SIZE_LARGE:
-                        default:
-                            return RecyclerViewAttributesOEMV1.SIZE_LARGE;
-                    }
-                }
-
-                @Override
-                public LayoutStyleOEMV1 getLayoutStyle() {
-                    return layoutStyle;
-                }
-            };
-        }
-        return oemAttrs;
-    }
-
-    private static ListItemOEMV1 toOemListItem(CarUiListItem item) {
-        if (item instanceof CarUiHeaderListItem) {
-            CarUiHeaderListItem header = (CarUiHeaderListItem) item;
-            return new HeaderListItemOEMV1.Builder(new SpannableString(header.getTitle()))
-                    .setBody(new SpannableString(header.getBody()))
-                    .build();
-        } else if (item instanceof CarUiContentListItem) {
-            CarUiContentListItem contentItem = (CarUiContentListItem) item;
-
-            ContentListItemOEMV1.Builder builder = new ContentListItemOEMV1.Builder(
-                    toOemListItemAction(contentItem.getAction()));
-
-            if (contentItem.getTitle() != null) {
-                builder.setTitle(
-                        new SpannableString(contentItem.getTitle().getPreferredText()));
-            }
-
-            if (contentItem.getBody() != null) {
-                builder.setBody(new SpannableString(
-                        CarUiText.combineMultiLine(contentItem.getBody())));
-            }
-
-            builder.setIcon(contentItem.getIcon(),
-                    toOemListItemIconType(contentItem.getPrimaryIconType()));
-
-            if (contentItem.getAction() == CarUiContentListItem.Action.ICON) {
-                Consumer<ContentListItemOEMV1> listener =
-                        contentItem.getSupplementalIconOnClickListener() != null
-                                ? oemItem ->
-                                contentItem.getSupplementalIconOnClickListener().onClick(
-                                        contentItem) : null;
-                builder.setSupplementalIcon(contentItem.getSupplementalIcon(), listener);
-            }
-
-            if (contentItem.getOnClickListener() != null) {
-                Consumer<ContentListItemOEMV1> listener =
-                        contentItem.getOnClickListener() != null
-                                ? oemItem ->
-                                contentItem.getOnClickListener().onClick(contentItem) : null;
-                builder.setOnItemClickedListener(listener);
-            }
-
-            builder.setOnCheckedChangeListener(
-                    oemItem -> contentItem.setChecked(oemItem.isChecked()))
-                    .setActionDividerVisible(contentItem.isActionDividerVisible())
-                    .setEnabled(contentItem.isEnabled())
-                    .setChecked(contentItem.isChecked())
-                    .setActivated(contentItem.isActivated());
-            return builder.build();
-        } else {
-            throw new IllegalStateException("Unexpected list item type");
-        }
-    }
-
-    private static ContentListItemOEMV1.Action toOemListItemAction(
-            CarUiContentListItem.Action action) {
-        switch (action) {
-            case NONE:
-                return ContentListItemOEMV1.Action.NONE;
-            case SWITCH:
-                return ContentListItemOEMV1.Action.SWITCH;
-            case CHECK_BOX:
-                return ContentListItemOEMV1.Action.CHECK_BOX;
-            case RADIO_BUTTON:
-                return ContentListItemOEMV1.Action.RADIO_BUTTON;
-            case ICON:
-                return ContentListItemOEMV1.Action.ICON;
-            case CHEVRON:
-                return ContentListItemOEMV1.Action.CHEVRON;
-            default:
-                throw new IllegalStateException("Unexpected list item action type");
-        }
-    }
-
-    private static ContentListItemOEMV1.IconType toOemListItemIconType(
-            CarUiContentListItem.IconType iconType) {
-        switch (iconType) {
-            case CONTENT:
-                return ContentListItemOEMV1.IconType.CONTENT;
-            case STANDARD:
-                return ContentListItemOEMV1.IconType.STANDARD;
-            case AVATAR:
-                return ContentListItemOEMV1.IconType.AVATAR;
-            default:
-                throw new IllegalStateException("Unexpected list item icon type");
-        }
-    }
-}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java
deleted file mode 100644
index a59b187..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryFactorySingleton.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrarysupport;
-
-import static java.util.Objects.requireNonNull;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.car.ui.R;
-import com.android.car.ui.utils.CarUiUtils;
-
-import java.io.File;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This is a singleton that contains a {@link SharedLibraryFactory}. That SharedLibraryFactory
- * is used to create UI components that we want to be customizable by the OEM.
- */
-@SuppressWarnings("AndroidJdkLibsChecker")
-public final class SharedLibraryFactorySingleton {
-
-    private static final String TAG = "carui";
-    private static SharedLibraryFactory sInstance;
-
-    /*
-     ********************************************
-     *               WARNING                    *
-     ********************************************
-     * The OEM APIs as they appear on this      *
-     * branch of android are not finalized!     *
-     * If a shared library is built using them, *
-     * it will cause apps to crash!             *
-     *                                          *
-     * Please only use a shared library with    *
-     * a later version of car-ui-lib.           *
-     ********************************************
-     */
-    private static boolean sSharedLibEnabled = false;
-
-    /**
-     * Get the {@link SharedLibraryFactory}.
-     *
-     * If this is the first time the method is being called, it will initialize it using reflection
-     * to check for the existence of a shared library, and resolving the appropriate version
-     * of the shared library to use.
-     */
-    public static SharedLibraryFactory get(Context context) {
-        if (sInstance != null) {
-            return sInstance;
-        }
-
-        context = context.getApplicationContext();
-
-        if (!sSharedLibEnabled) {
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        String sharedLibPackageName = CarUiUtils.getSystemProperty(context.getResources(),
-                R.string.car_ui_shared_library_package_system_property_name);
-
-        if (TextUtils.isEmpty(sharedLibPackageName)) {
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        PackageInfo sharedLibPackageInfo;
-        try {
-            sharedLibPackageInfo = context.getPackageManager()
-                    .getPackageInfo(sharedLibPackageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Could not load CarUi shared library, package "
-                    + sharedLibPackageName + " was not found.");
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        Context applicationContext = context.getApplicationContext();
-        if (applicationContext instanceof SharedLibraryConfigProvider) {
-            Set<SharedLibrarySpecifier> deniedPackages =
-                    ((SharedLibraryConfigProvider) applicationContext).getSharedLibraryDenyList();
-            if (deniedPackages != null && deniedPackages.stream()
-                    .anyMatch(specs -> specs.matches(sharedLibPackageInfo))) {
-                Log.i(TAG, "Package " + context.getPackageName()
-                        + " denied loading shared library " + sharedLibPackageName);
-                sInstance = new SharedLibraryFactoryStub(context);
-                return sInstance;
-            }
-        }
-
-        Context sharedLibraryContext;
-        try {
-            sharedLibraryContext = context.createPackageContext(
-                    sharedLibPackageName,
-                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Could not load CarUi shared library, package "
-                    + sharedLibPackageName + " was not found.");
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        AdapterClassLoader adapterClassLoader =
-                instantiateClassLoader(context.getApplicationInfo(),
-                        requireNonNull(SharedLibraryFactorySingleton.class.getClassLoader()),
-                        sharedLibraryContext.getClassLoader());
-
-        try {
-            Class<?> oemApiUtilClass = adapterClassLoader
-                    .loadClass("com.android.car.ui.sharedlibrarysupport.OemApiUtil");
-            Method getSharedLibraryFactoryMethod = oemApiUtilClass.getDeclaredMethod(
-                    "getSharedLibraryFactory", Context.class, String.class);
-            getSharedLibraryFactoryMethod.setAccessible(true);
-            sInstance = (SharedLibraryFactory) getSharedLibraryFactoryMethod
-                    .invoke(null, sharedLibraryContext, context.getPackageName());
-        } catch (ReflectiveOperationException e) {
-            Log.e(TAG, "Could not load CarUi shared library", e);
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        if (sInstance == null) {
-            Log.e(TAG, "Could not load CarUi shared library");
-            sInstance = new SharedLibraryFactoryStub(context);
-            return sInstance;
-        }
-
-        Log.i(TAG, "Loaded shared library " + sharedLibPackageName
-                + " version " + sharedLibPackageInfo.getLongVersionCode()
-                + " for package " + context.getPackageName());
-
-        return sInstance;
-    }
-
-    /**
-     * This method globally enables/disables the shared library. It only applies upon the next
-     * call to {@link #get}, components that have already been created won't switch between
-     * the shared/static library implementations.
-     * <p>
-     * This method is @VisibleForTesting so that unit tests can run both with and without
-     * the shared library. Since it's tricky to use correctly, real apps shouldn't use it.
-     * Instead, apps should use {@link SharedLibraryConfigProvider} to control if their
-     * shared library is disabled.
-     */
-    @VisibleForTesting
-    public static void setSharedLibEnabled(boolean sharedLibEnabled) {
-        sSharedLibEnabled = sharedLibEnabled;
-        // Cause the next call to get() to reinitialize the shared library
-        sInstance = null;
-    }
-
-    private SharedLibraryFactorySingleton() {}
-
-    @NonNull
-    private static AdapterClassLoader instantiateClassLoader(@NonNull ApplicationInfo appInfo,
-            @NonNull ClassLoader parent, @NonNull ClassLoader sharedlibraryClassLoader) {
-        // All this apk loading code is copied from another Google app
-        List<String> libraryPaths = new ArrayList<>(3);
-        if (appInfo.nativeLibraryDir != null) {
-            libraryPaths.add(appInfo.nativeLibraryDir);
-        }
-        if ((appInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0) {
-            for (String abi : getSupportedAbisForCurrentRuntime()) {
-                libraryPaths.add(appInfo.sourceDir + "!/lib/" + abi);
-            }
-        }
-
-        String flatLibraryPaths = (libraryPaths.size() == 0
-                ? null : TextUtils.join(File.pathSeparator, libraryPaths));
-
-        String apkPaths = appInfo.sourceDir;
-        if (appInfo.sharedLibraryFiles != null && appInfo.sharedLibraryFiles.length > 0) {
-            // Unless you pass PackageManager.GET_SHARED_LIBRARY_FILES this will always be null
-            // HOWEVER, if you running on a device with F5 active, the module's dex files are
-            // always listed in ApplicationInfo.sharedLibraryFiles and should be included in
-            // the classpath.
-            apkPaths +=
-                    File.pathSeparator + TextUtils.join(File.pathSeparator,
-                            appInfo.sharedLibraryFiles);
-        }
-
-        return new AdapterClassLoader(apkPaths, flatLibraryPaths, parent, sharedlibraryClassLoader);
-    }
-
-    private static List<String> getSupportedAbisForCurrentRuntime() {
-        List<String> abis = new ArrayList<>();
-        if (Process.is64Bit()) {
-            Collections.addAll(abis, Build.SUPPORTED_64_BIT_ABIS);
-        } else {
-            Collections.addAll(abis, Build.SUPPORTED_32_BIT_ABIS);
-        }
-        return abis;
-    }
-}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProviderAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProviderAdapterV1.java
deleted file mode 100644
index ca14aea..0000000
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/sharedlibrarysupport/SharedLibraryVersionProviderAdapterV1.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrarysupport;
-
-import android.content.Context;
-
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryVersionProviderOEMV1;
-
-/**
- * This class is an wrapper around {@link SharedLibraryVersionProviderOEMV1} that implements
- * {@link SharedLibraryVersionProvider}, to provide a version-agnostic way of interfacing with
- * the OEM's SharedLibraryFactoryVersionProvider.
- */
-final class SharedLibraryVersionProviderAdapterV1 implements SharedLibraryVersionProvider {
-
-    private SharedLibraryVersionProviderOEMV1 mOemProvider;
-
-    SharedLibraryVersionProviderAdapterV1(
-            SharedLibraryVersionProviderOEMV1 oemVersionProvider) {
-        mOemProvider = oemVersionProvider;
-    }
-
-    @Override
-    public Object getSharedLibraryFactory(int maxVersion, Context context, String packageName) {
-        return mOemProvider.getSharedLibraryFactory(maxVersion, context, packageName);
-    }
-}
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
index 8a66906..aad3457 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/CarUiEditText.java
@@ -16,6 +16,10 @@
 
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -29,7 +33,9 @@
  * Edit text supporting the callbacks from the IMS. This will be useful in widescreen IME mode to
  * allow car-ui-lib to receive responses (like onClick events) from the IMS
  */
+@SuppressLint("AppCompatCustomView")
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 class CarUiEditText extends EditText {
 
     @Nullable
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ImageViewListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ImageViewListener.java
index 106b612..1d71c10 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ImageViewListener.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ImageViewListener.java
@@ -16,13 +16,22 @@
 
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.widget.ImageView;
 
 import java.util.function.Consumer;
 
+/**
+ * Listener class for setting Drawable for {@link DeprecatedTabWrapper}
+ */
+@SuppressLint("AppCompatCustomView")
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public final class ImageViewListener extends ImageView {
 
     private Consumer<Drawable> mImageDrawableListener;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
index 0414d26..12caa19 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItem.java
@@ -15,6 +15,9 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
@@ -47,6 +50,7 @@
  * <p>Some properties can be changed after the creating a MenuItem, but others require being set
  * with a {@link Builder}.
  */
+@TargetApi(MIN_TARGET_API)
 public class MenuItem {
 
     private final Context mContext;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemAdapterV1.java
index f134c25..9cec730 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemAdapterV1.java
@@ -16,135 +16,96 @@
 
 package com.android.car.ui.toolbar;
 
-import static com.android.car.ui.utils.CarUiUtils.charSequenceToString;
+import androidx.annotation.NonNull;
 
-import android.graphics.drawable.Drawable;
-
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.MenuItemOEMV1;
-
-import java.util.function.Consumer;
+import com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * Adapts a {@link com.android.car.ui.toolbar.MenuItem} into a
- * {@link com.android.car.ui.sharedlibrary.oemapis.toolbar.MenuItemOEMV1}
+ * {@link com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1}
  */
-@SuppressWarnings("AndroidJdkLibsChecker")
-public class MenuItemAdapterV1 implements MenuItemOEMV1 {
+public class MenuItemAdapterV1 {
 
+    @NonNull
+    private final ToolbarControllerAdapterV1 mToolbar;
+    @NonNull
     private final MenuItem mClientMenuItem;
-    private Consumer<MenuItemOEMV1> mUpdateListener;
+    @NonNull
+    private MenuItemOEMV1 mPluginMenuItem = MenuItemOEMV1.builder().build();
 
-    // This needs to be a member variable because it's only held with a weak listener
-    // elsewhere.
+    @SuppressWarnings("FieldCanBeLocal") // Used with weak references
     private final MenuItem.Listener mClientListener = menuItem -> {
-        if (mUpdateListener != null) {
-            mUpdateListener.accept(this);
-        }
+        updateMenuItem();
+        updateMenuItems();
     };
 
-    public MenuItemAdapterV1(MenuItem item) {
+    public MenuItemAdapterV1(@NonNull ToolbarControllerAdapterV1 toolbar, @NonNull MenuItem item) {
+        mToolbar = toolbar;
         mClientMenuItem = item;
         item.setListener(mClientListener);
+        updateMenuItem();
     }
 
-    @Override
-    public void setUpdateListener(Consumer<MenuItemOEMV1> listener) {
-        mUpdateListener = listener;
+    private void updateMenuItems() {
+        mToolbar.updateMenuItems();
     }
 
-    @Override
-    public void performClick() {
-        mClientMenuItem.performClick();
+    // Recreates mPluginMenuItem from mClientMenuItem
+    private void updateMenuItem() {
+        MenuItemOEMV1.Builder builder = mPluginMenuItem.copy()
+                .setKey(mClientMenuItem.hashCode())
+                .setTitle(CarUiUtils.charSequenceToString(mClientMenuItem.getTitle()))
+                .setIcon(mClientMenuItem.getIcon())
+                .setEnabled(mClientMenuItem.isEnabled())
+                .setPrimary(mClientMenuItem.isPrimary())
+                .setRestricted(mClientMenuItem.isRestricted())
+                .setShowIconAndTitle(mClientMenuItem.isShowingIconAndTitle())
+                .setDisplayBehavior(convertDisplayBehavior(mClientMenuItem.getDisplayBehavior()));
+
+        if (mClientMenuItem.isCheckable()) {
+            builder.setCheckable(true)
+                    .setChecked(mClientMenuItem.isChecked());
+        }
+
+        if (mClientMenuItem.isActivatable()) {
+            builder.setActivatable(true)
+                    .setActivated(mClientMenuItem.isActivated());
+        }
+
+        MenuItem.OnClickListener onClickListener = mClientMenuItem.getOnClickListener();
+        if (onClickListener != null || mClientMenuItem.isActivatable()
+                || mClientMenuItem.isCheckable() || mClientMenuItem.isRestricted()) {
+            builder.setOnClickListener(mClientMenuItem::performClick);
+        } else {
+            builder.setOnClickListener(null);
+        }
+
+        mPluginMenuItem = builder.build();
     }
 
-    @Override
-    public int getId() {
-        return mClientMenuItem.getId();
+    @NonNull
+    public MenuItemOEMV1 getPluginMenuItem() {
+        return mPluginMenuItem;
     }
 
-    @Override
-    public boolean isEnabled() {
-        return mClientMenuItem.isEnabled();
+    @NonNull
+    public MenuItem getClientMenuItem() {
+        return mClientMenuItem;
     }
 
-    @Override
-    public boolean isCheckable() {
-        return mClientMenuItem.isCheckable();
-    }
-
-    @Override
-    public boolean isChecked() {
-        return mClientMenuItem.isChecked();
-    }
-
-    @Override
-    public boolean isTinted() {
-        return mClientMenuItem.isTinted();
-    }
-
-    @Override
     public boolean isVisible() {
         return mClientMenuItem.isVisible();
     }
 
-    @Override
-    public boolean isActivatable() {
-        return mClientMenuItem.isActivatable();
-    }
-
-    @Override
-    public boolean isActivated() {
-        return mClientMenuItem.isActivated();
-    }
-
-    @Override
-    public String getTitle() {
-        return charSequenceToString(mClientMenuItem.getTitle());
-    }
-
-    @Override
-    public boolean isRestricted() {
-        return mClientMenuItem.isRestricted();
-    }
-
-    @Override
-    public boolean isShowingIconAndTitle() {
-        return mClientMenuItem.isShowingIconAndTitle();
-    }
-
-    @Override
-    public boolean isClickable() {
-        return mClientMenuItem.getOnClickListener() != null
-                || isCheckable()
-                || isActivatable();
-    }
-
-    @Override
-    public int getDisplayBehavior() {
-        MenuItem.DisplayBehavior displayBehavior = mClientMenuItem.getDisplayBehavior();
-        if (displayBehavior == MenuItem.DisplayBehavior.NEVER) {
-            return MenuItemOEMV1.DISPLAY_BEHAVIOR_NEVER;
-        } else {
-            return MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS;
+    private static int convertDisplayBehavior(MenuItem.DisplayBehavior displayBehavior) {
+        switch (displayBehavior) {
+            case ALWAYS:
+                return MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS;
+            case NEVER:
+                return MenuItemOEMV1.DISPLAY_BEHAVIOR_NEVER;
+            default:
+                throw new IllegalArgumentException("Unknown display behavior!");
         }
     }
-
-    @Override
-    public Drawable getIcon() {
-        return mClientMenuItem.getIcon();
-    }
-
-    @Override
-    public boolean isPrimary() {
-        return mClientMenuItem.isPrimary();
-    }
-
-    public boolean isSearch() {
-        return mClientMenuItem.isSearch();
-    }
-
-    /** Delegates to {@link MenuItem#setVisible(boolean)} */
-    public void setVisible(boolean visible) {
-        mClientMenuItem.setVisible(visible);
-    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
index a757ea6..fcce52b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemRenderer.java
@@ -15,8 +15,10 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,9 +35,10 @@
 import java.util.function.Consumer;
 
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 class MenuItemRenderer implements MenuItem.Listener {
 
-    private static final int[] RESTRICTED_STATE = new int[] {R.attr.state_ux_restricted};
+    private static final int[] RESTRICTED_STATE = new int[]{R.attr.state_ux_restricted};
 
     private final int mMenuItemIconSize;
 
@@ -81,7 +84,7 @@
                 ? R.layout.car_ui_toolbar_menu_item_primary
                 : R.layout.car_ui_toolbar_menu_item;
         inflater.inflate(layout, mParentView, (View view, int resid,
-                ViewGroup parent) -> {
+                                               ViewGroup parent) -> {
             mView = view;
 
             mIconContainer =
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemXmlParserUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemXmlParserUtil.java
index 604152b..8abce12 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemXmlParserUtil.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/MenuItemXmlParserUtil.java
@@ -15,6 +15,9 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -44,6 +47,7 @@
  * Apps don't need to use this, as there also exists a {@link ToolbarController#setMenuItems(int)}
  * function that will include the same functionality.
  */
+@TargetApi(MIN_TARGET_API)
 public class MenuItemXmlParserUtil {
 
     private MenuItemXmlParserUtil() {}
@@ -86,7 +90,7 @@
         try {
             int id = a.getResourceId(R.styleable.CarUiToolbarMenuItem_id, View.NO_ID);
             String title = a.getString(R.styleable.CarUiToolbarMenuItem_title);
-            Drawable icon = a.getDrawable(R.styleable.CarUiToolbarMenuItem_icon);
+            Drawable icon = a.getDrawable(R.styleable.CarUiToolbarMenuItem_carUiIcon);
             boolean isSearch = a.getBoolean(R.styleable.CarUiToolbarMenuItem_search, false);
             boolean isSettings = a.getBoolean(R.styleable.CarUiToolbarMenuItem_settings, false);
             boolean tinted = a.getBoolean(R.styleable.CarUiToolbarMenuItem_tinted, true);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerAdapterV1.java
index b03bb73..9050cfe 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerAdapterV1.java
@@ -18,7 +18,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ProgressBarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ProgressBarControllerOEMV1;
 
 class ProgressBarControllerAdapterV1 implements ProgressBarController {
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
index 07a59b2..9b48063 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ProgressBarControllerImpl.java
@@ -15,6 +15,9 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.view.View;
 import android.widget.ProgressBar;
 
@@ -26,6 +29,7 @@
  * <p>This class accepts a {@link ProgressBar} in it's constructor and forwards the methods
  * of {@link ProgressBarController} to it.
  */
+@TargetApi(MIN_TARGET_API)
 class ProgressBarControllerImpl implements ProgressBarController {
 
     @NonNull
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchConfig.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchConfig.java
index 917b397..4a6e415 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchConfig.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchConfig.java
@@ -33,12 +33,24 @@
  */
 public final class SearchConfig {
 
+    /**
+     * Callback to be invoked when user clicks the back button on IME to close.
+     */
+    public interface OnBackClickedListener {
+        /**
+         * Called when user clicks the back button on widescreen IME.
+         */
+        void onClick();
+    }
+
     @Nullable
     private final View mSearchResultsView;
     @Nullable
     private final Drawable mSearchResultsInputViewIcon;
     @Nullable
     private final List<? extends CarUiImeSearchListItem> mSearchResultItems;
+    @Nullable
+    private OnBackClickedListener mOnBackClickedListener;
 
     /**
      * Returns the view set by {@link SearchConfigBuilder#setSearchResultsView(View)}
@@ -64,10 +76,20 @@
         return mSearchResultItems;
     }
 
+    /**
+     * Returns the listeners set by
+     * {@link SearchConfigBuilder#setOnBackClickedListener(OnBackClickedListener)}
+     */
+    @Nullable
+    public OnBackClickedListener getOnBackClickedListener() {
+        return mOnBackClickedListener;
+    }
+
     private SearchConfig(SearchConfigBuilder builder) {
         mSearchResultItems = builder.mSearchResultItems;
         mSearchResultsInputViewIcon = builder.mSearchResultsInputViewIcon;
         mSearchResultsView = builder.mSearchResultsView;
+        mOnBackClickedListener = builder.mOnBackClickedListener;
     }
 
     /**
@@ -88,6 +110,8 @@
         private Drawable mSearchResultsInputViewIcon;
         @Nullable
         private List<? extends CarUiImeSearchListItem> mSearchResultItems;
+        @Nullable
+        private OnBackClickedListener mOnBackClickedListener;
 
         private SearchConfigBuilder() {
 
@@ -102,10 +126,18 @@
         }
 
         /**
+         * Set the {@link OnBackClickedListener}
+         */
+        public SearchConfigBuilder setOnBackClickedListener(OnBackClickedListener listener) {
+            mOnBackClickedListener = listener;
+            return this;
+        }
+
+        /**
          * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
          * template. This method should be called when system is running in a wide screen mode. Apps
          * can check that by using
-         * {@link com.android.car.ui.toolbar.ToolbarController##canShowSearchResultItems()}
+         * {@link com.android.car.ui.toolbar.ToolbarController#canShowSearchResultItems()}
          * Else, this method will throw an {@link IllegalStateException}
          */
         public SearchConfigBuilder setSearchResultItems(
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
index 5f6a72b..1e39410 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchView.java
@@ -15,8 +15,11 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
+import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.Editable;
@@ -35,6 +38,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
 
 import java.util.Collections;
 import java.util.Set;
@@ -44,17 +48,23 @@
  * A search view used by {@link Toolbar}.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(TARGET_API_R)
 public class SearchView extends ConstraintLayout {
 
     private final InputMethodManager mInputMethodManager;
     private final SearchWidescreenController mSearchWidescreenController;
     private final ImageView mIcon;
     private final EditText mSearchText;
+    private CharSequence mSearchHint;
+    private boolean mIsRestricted;
     private final View mCloseIcon;
     private final int mStartPaddingWithoutIcon;
     private final int mStartPadding;
     private final int mEndPadding;
 
+    @NonNull
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new UxRestrictionChangedListener();
     private Set<Consumer<String>> mSearchListeners = Collections.emptySet();
     private Set<Runnable> mSearchCompletedListeners =
             Collections.emptySet();
@@ -150,6 +160,18 @@
         }
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        CarUxRestrictionsUtil.getInstance(getContext()).register(mListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        CarUxRestrictionsUtil.getInstance(getContext()).unregister(mListener);
+    }
+
     private boolean isEnter(KeyEvent event) {
         boolean result = false;
         if (event != null) {
@@ -223,6 +245,11 @@
      * @param hint A CharSequence of the search hint.
      */
     public void setHint(CharSequence hint) {
+        mSearchHint = hint;
+        if (mIsRestricted) {
+            return;
+        }
+
         mSearchText.setHint(hint);
     }
 
@@ -270,6 +297,29 @@
         }
     }
 
+    private void enableRestriction() {
+        mIsRestricted = true;
+
+        if (mSearchText == null) {
+            return;
+        }
+
+        mSearchText.setHint(mSearchText.getContext().getString(
+                R.string.car_ui_restricted_while_driving));
+        mSearchText.setEnabled(false);
+    }
+
+    private void disableRestriction() {
+        mIsRestricted = false;
+
+        if (mSearchText == null) {
+            return;
+        }
+
+        mSearchText.setHint(mSearchHint);
+        mSearchText.setEnabled(true);
+    }
+
     /**
      * Sets the text being searched.
      */
@@ -277,4 +327,19 @@
         mSearchText.setText(query);
         mSearchText.setSelection(mSearchText.getText().length());
     }
+
+    private class UxRestrictionChangedListener implements
+            CarUxRestrictionsUtil.OnUxRestrictionsChangedListener {
+
+        @Override
+        public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+            boolean isKeyboardRestricted = (carUxRestrictions.getActiveRestrictions()
+                    & CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD) != 0;
+            if (isKeyboardRestricted) {
+                enableRestriction();
+            } else if (!isKeyboardRestricted) {
+                disableRestriction();
+            }
+        }
+    }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchWidescreenController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchWidescreenController.java
index 23afd77..f67cb87 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchWidescreenController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/SearchWidescreenController.java
@@ -17,6 +17,7 @@
 
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_DISPLAY_ID;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HEIGHT;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.CONTENT_AREA_SURFACE_HOST_TOKEN;
@@ -26,6 +27,8 @@
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.SEARCH_RESULT_SUPPLEMENTAL_ICON_ID_LIST;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_CLEAR_DATA_ACTION;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_EXTRACTED_TEXT_ICON;
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ON_BACK_CLICKED_ACTION;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_POST_LOAD_SEARCH_RESULTS_ACTION;
 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_SEARCH_RESULTS;
 import static com.android.car.ui.utils.CarUiUtils.getBooleanSystemProperty;
@@ -52,6 +55,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import com.android.car.ui.CarUiText;
 import com.android.car.ui.R;
@@ -59,6 +63,8 @@
 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
 import com.android.car.ui.recyclerview.CarUiContentListItem;
 import com.android.car.ui.recyclerview.CarUiRecyclerView;
+import com.android.car.ui.toolbar.SearchConfig.OnBackClickedListener;
+import com.android.car.ui.utils.CarUiUtils;
 
 import java.util.List;
 import java.util.function.BiConsumer;
@@ -75,6 +81,7 @@
  * the TextView has been set, it will just wait for the TextView before doing anything.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@RequiresApi(TARGET_API_R)
 public class SearchWidescreenController {
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -124,7 +131,7 @@
     private View getSearchResultsView() {
         View view = mSearchConfig.getSearchResultsView();
         if (view instanceof CarUiRecyclerView) {
-            return ((CarUiRecyclerView) view).getContainer();
+            return ((CarUiRecyclerView) view).getView();
         }
         return view;
     }
@@ -324,6 +331,13 @@
         mSurfaceWidth = width;
 
         Bundle bundle = new Bundle();
+        if (mSearchConfig.getSearchResultsInputViewIcon() != null) {
+            Bitmap bitmap = CarUiUtils.drawableToBitmap(
+                    mSearchConfig.getSearchResultsInputViewIcon());
+            byte[] byteArray = bitmapToByteArray(bitmap);
+            bundle.putByteArray(WIDE_SCREEN_EXTRACTED_TEXT_ICON, byteArray);
+        }
+
         bundle.putParcelable(CONTENT_AREA_SURFACE_PACKAGE,
                 mSurfaceControlViewHost.getSurfacePackage());
         mInputMethodManager.sendAppPrivateCommand(mTextView, WIDE_SCREEN_ACTION, bundle);
@@ -363,6 +377,13 @@
                 onPostLoadSearchResults();
             }
 
+            if (WIDE_SCREEN_ON_BACK_CLICKED_ACTION.equals(action)) {
+                OnBackClickedListener listener = mSearchConfig.getOnBackClickedListener();
+                if (listener != null) {
+                    listener.onClick();
+                }
+            }
+
             if (data == null) {
                 return;
             }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Tab.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Tab.java
index 63b3cd1..c71d8ab 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Tab.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/Tab.java
@@ -35,11 +35,13 @@
     private final Drawable mIcon;
     @Nullable
     private final Consumer<Tab> mSelectedListener;
+    private final boolean mTinted;
 
     private Tab(@NonNull Builder builder) {
         mText = builder.mText;
         mIcon = builder.mIcon;
         mSelectedListener = builder.mSelectedListener;
+        mTinted = builder.mTinted;
     }
 
     /** Gets the tab's text */
@@ -60,11 +62,21 @@
         return mSelectedListener;
     }
 
+    /** Gets if the icon should be tinted to match the style of the toolbar. Default true. */
+    public boolean isTinted() {
+        return mTinted;
+    }
+
     /** Creates a new {@link Builder} */
     public static Builder builder() {
         return new Builder();
     }
 
+    /** Creates a new {@link Builder} that is initialized as a copy of this tab. */
+    public Builder copy() {
+        return new Builder(this);
+    }
+
     /** Builder for {@link Tab} */
     public static class Builder {
         @Nullable
@@ -73,10 +85,18 @@
         private Drawable mIcon = null;
         @Nullable
         private Consumer<Tab> mSelectedListener = null;
+        private boolean mTinted = true;
 
         private Builder() {
         }
 
+        private Builder(Tab tab) {
+            mText = tab.mText;
+            mIcon = tab.mIcon;
+            mSelectedListener = tab.mSelectedListener;
+            mTinted  = tab.mTinted;
+        }
+
         /** Sets the tab's text */
         public Builder setText(String text) {
             mText = text;
@@ -95,6 +115,13 @@
             return this;
         }
 
+
+        /** Sets if the icon should be tinted to match the style of the toolbar. Default true. */
+        public Builder setTinted(boolean tinted) {
+            mTinted = tinted;
+            return this;
+        }
+
         /** Builds the final {@link Tab} */
         public Tab build() {
             return new Tab(this);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabAdapterV1.java
index 543a10d..dc052f7 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabAdapterV1.java
@@ -16,25 +16,31 @@
 
 package com.android.car.ui.toolbar;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.TabOEMV1;
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
+
+import com.android.car.ui.plugin.oemapis.toolbar.TabOEMV1;
 
 import java.util.function.Consumer;
 
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 class TabAdapterV1 {
 
     private final Tab mClientTab;
-    private final TabOEMV1 mSharedLibraryTab;
+    private final TabOEMV1 mPluginTab;
 
     TabAdapterV1(Tab clientTab) {
         mClientTab = clientTab;
         Consumer<Tab> selectedListener = mClientTab.getSelectedListener();
-        mSharedLibraryTab = TabOEMV1.builder()
+        mPluginTab = TabOEMV1.builder()
                 .setIcon(mClientTab.getIcon())
                 .setTitle(mClientTab.getText())
                 .setOnSelectedListener(selectedListener == null
                         ? null
                         : () -> selectedListener.accept(mClientTab))
+                .setTinted(mClientTab.isTinted())
                 .build();
     }
 
@@ -42,7 +48,7 @@
         return mClientTab;
     }
 
-    public TabOEMV1 getSharedLibraryTab() {
-        return mSharedLibraryTab;
+    public TabOEMV1 getPluginTab() {
+        return mPluginTab;
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java
index cfd6acf..b924d0c 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TabLayout.java
@@ -15,8 +15,10 @@
  */
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -56,6 +58,7 @@
  * <p>Touch feedback is using @android:attr/selectableItemBackground.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public class TabLayout extends LinearLayout {
     @LayoutRes
     private final int mTabLayoutRes;
@@ -106,7 +109,7 @@
 
     /**
      * Returns if this TabLayout has tabs. That is, if the most recent call to
-     * {@link #setTabs(List)} contained a non-empty list.
+     * {@link #setTabs(List, int)} contained a non-empty list.
      */
     public boolean hasTabs() {
         return !mTabs.isEmpty();
@@ -114,8 +117,8 @@
 
     /** Set the tab at given position as the current selected tab. */
     public void selectTab(int position) {
-        if (position < 0 || position > mTabs.size()) {
-            position = mTabs.isEmpty() ? -1 : 0;
+        if (position < 0 || position >= mTabs.size()) {
+            throw new IllegalArgumentException("Tab position is invalid: " + position);
         }
         if (position == mSelectedTab) {
             return;
@@ -126,12 +129,10 @@
         presentTabView(oldPosition);
         presentTabView(position);
 
-        if (position >= 0) {
-            com.android.car.ui.toolbar.Tab tab = mTabs.get(position);
-            Consumer<com.android.car.ui.toolbar.Tab> listener = tab.getSelectedListener();
-            if (listener != null) {
-                listener.accept(tab);
-            }
+        com.android.car.ui.toolbar.Tab tab = mTabs.get(position);
+        Consumer<com.android.car.ui.toolbar.Tab> listener = tab.getSelectedListener();
+        if (listener != null) {
+            listener.accept(tab);
         }
     }
 
@@ -146,8 +147,8 @@
     }
 
     private void presentTabView(int position) {
-        if (position < 0 || position > mTabs.size()) {
-            return;
+        if (position < 0 || position >= mTabs.size()) {
+            throw new IllegalArgumentException("Tab position is invalid: " + position);
         }
         View tabView = getChildAt(position);
         com.android.car.ui.toolbar.Tab tab = mTabs.get(position);
@@ -155,9 +156,17 @@
         TextView textView = requireViewByRefId(tabView, R.id.car_ui_toolbar_tab_item_text);
 
         tabView.setOnClickListener(view -> selectTab(position));
-        textView.setText(tab.getText());
-        iconView.setImageDrawable(tab.getIcon());
         tabView.setActivated(position == mSelectedTab);
+
+        if (tab.isTinted()) {
+            iconView.setImageTintList(getContext()
+                    .getColorStateList(R.color.car_ui_toolbar_tab_item_selector));
+        } else {
+            iconView.setImageTintList(null);
+        }
+        iconView.setImageDrawable(tab.getIcon());
+
+        textView.setText(tab.getText());
         textView.setTextAppearance(position == mSelectedTab
                 ? R.style.TextAppearance_CarUi_Widget_Toolbar_Tab_Selected
                 : R.style.TextAppearance_CarUi_Widget_Toolbar_Tab);
@@ -172,7 +181,6 @@
     public static class Tab {
         private final Drawable mIcon;
         private final CharSequence mText;
-        private boolean mIsSelected;
 
         public Tab(@Nullable Drawable icon, @Nullable CharSequence text) {
             mIcon = icon;
@@ -184,25 +192,9 @@
             textView.setText(mText);
         }
 
-        /**
-         * Do not use, this method is here for the shared library adapters, which cannot
-         * call the protected version due to being in a different classloader.
-         */
-        public final void bindTextPublic(TextView textView) {
-            bindText(textView);
-        }
-
         /** Set icon drawable. TODO(b/139444064): revise this api.*/
         protected void bindIcon(ImageView imageView) {
             imageView.setImageDrawable(mIcon);
         }
-
-        /**
-         * Do not use, this method is here for the shared library adapters, which cannot
-         * call the protected version due to being in a different classloader.
-         */
-        public final void bindIconPublic(ImageView imageView) {
-            bindIcon(imageView);
-        }
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TextViewListener.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TextViewListener.java
index ca6152a..7bd9386 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TextViewListener.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/TextViewListener.java
@@ -16,12 +16,21 @@
 
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.widget.TextView;
 
 import java.util.function.Consumer;
 
+/**
+ * Listener for setting text on {@link DeprecatedTabWrapper}
+ */
+@SuppressLint("AppCompatCustomView")
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public final class TextViewListener extends TextView {
     private Consumer<CharSequence> mTextListener;
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
index cf524fe..5c263be 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarController.java
@@ -25,6 +25,7 @@
 import androidx.annotation.StringRes;
 import androidx.annotation.XmlRes;
 
+import com.android.car.ui.CarUiText;
 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
 import com.android.car.ui.recyclerview.CarUiListItem;
 import com.android.car.ui.toolbar.SearchConfig.SearchConfigBuilder;
@@ -34,8 +35,11 @@
 import java.util.function.Supplier;
 
 /**
- * An interface for accessing a Chassis Toolbar, regardless of how the underlying
- * views are represented.
+ * An interface for accessing a Chassis Toolbar, regardless of how the underlying views are
+ * represented.
+ * <p>
+ * Rendered views will comply with
+ * <a href="https://source.android.com/devices/automotive/hmi/car_ui/appendix_b">customization guardrails</a>
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
 public interface ToolbarController {
@@ -52,7 +56,14 @@
      *
      * <p>The title may not always be shown, for example with one row layout with tabs.
      */
-    void setTitle(CharSequence title);
+    void setTitle(@Nullable CharSequence title);
+
+    /**
+     * Sets the title of the toolbar to a CharSequence.
+     *
+     * <p>The title may not always be shown, for example with one row layout with tabs.
+     */
+    void setTitle(@Nullable CarUiText title);
 
     /**
      * Gets the current toolbar title.
@@ -71,7 +82,14 @@
      *
      * <p>The title may not always be shown, for example with one row layout with tabs.
      */
-    void setSubtitle(CharSequence title);
+    void setSubtitle(@Nullable CharSequence text);
+
+    /**
+     * Sets the subtitle of the toolbar to a CharSequence.
+     *
+     * <p>The title may not always be shown, for example with one row layout with tabs.
+     */
+    void setSubtitle(@Nullable CarUiText text);
 
     /**
      * Gets the current toolbar subtitle.
@@ -222,6 +240,11 @@
     void setSearchMode(SearchMode mode);
 
     /**
+     * Returns the current search mode, set by {@link #setSearchMode}.
+     */
+    SearchMode getSearchMode();
+
+    /**
      * Sets the {@link Toolbar.NavButtonMode}
      *
      * @deprecated Use {@link #setNavButtonMode(NavButtonMode)} instead.
@@ -233,15 +256,9 @@
     void setNavButtonMode(NavButtonMode mode);
 
     /**
-     * Gets the {@link Toolbar.NavButtonMode}.
-     *
-     * @deprecated No equivalent replacement. Maintain the current mode locally in the app if
-     * needed. If this is for a test, you can use this espresso code to check that the toolbar
-     * back button is shown:
-     * {@code onView(withContentDescription("Back")).check(matches(isDisplayed()));}
+     * Gets the {@link NavButtonMode}.
      */
-    @Deprecated
-    Toolbar.NavButtonMode getNavButtonMode();
+    NavButtonMode getNavButtonMode();
 
     /** Show/hide the background. When hidden, the toolbar is completely transparent. */
     void setBackgroundShown(boolean shown);
@@ -264,7 +281,7 @@
      * child tags. See CarUiToolbarMenuItem in CarUi's attrs.xml for a list of available attributes.
      *
      * Example:
-     * <pre>
+     * <pre>{@code
      * <MenuItems>
      *     <MenuItem
      *         app:title="Foo"/>
@@ -278,7 +295,7 @@
      *         app:uxRestrictions="FULLY_RESTRICTED"
      *         app:onClick="xmlMenuItemClicked"/>
      * </MenuItems>
-     * </pre>
+     * }</pre>
      *
      * @return The MenuItems that were loaded from XML.
      * @see #setMenuItems(List)
@@ -473,7 +490,7 @@
     @Deprecated
     boolean unregisterOnBackListener(Toolbar.OnBackListener listener);
 
-    /** Registers a new {@link Supplier<Boolean>} to the list of listeners. */
+    /** Registers a new {@link Supplier} to the list of listeners. */
     void registerBackListener(Supplier<Boolean> listener);
 
     /** Unregisters an existing {@link Runnable} from the list of listeners. */
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerAdapterV1.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerAdapterV1.java
index 8742961..bd8ac3f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerAdapterV1.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerAdapterV1.java
@@ -16,16 +16,17 @@
 
 package com.android.car.ui.toolbar;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
 import static com.android.car.ui.utils.CarUiUtils.charSequenceToString;
 import static com.android.car.ui.utils.CarUiUtils.convertList;
 
 import static java.util.stream.Collectors.toList;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
-import android.util.Log;
 import android.view.View;
 
 import androidx.annotation.DrawableRes;
@@ -34,9 +35,11 @@
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
+import com.android.car.ui.CarUiText;
 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ImeSearchInterfaceOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ImeSearchInterfaceOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
 import com.android.car.ui.toolbar.Toolbar.OnBackListener;
 import com.android.car.ui.toolbar.Toolbar.OnSearchCompletedListener;
 import com.android.car.ui.toolbar.Toolbar.OnSearchListener;
@@ -52,16 +55,15 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 
 /**
- * Adapts a {@link com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1}
- * into a {@link ToolbarController}
+ * Adapts a {@link com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1} into a
+ * {@link ToolbarController}
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(TARGET_API_R)
 public final class ToolbarControllerAdapterV1 implements ToolbarController {
-
-    private static final String TAG = ToolbarControllerAdapterV1.class.getName();
-
     private final ToolbarControllerOEMV1 mOemToolbar;
     private final Context mContext;
 
@@ -81,6 +83,7 @@
     private final List<DeprecatedTabWrapper> mDeprecatedTabs = new ArrayList<>();
     private final SearchWidescreenController mSearchWidescreenController;
     private final boolean mSupportsImeSearch;
+    private boolean mBackgroundShown = true;
 
     public ToolbarControllerAdapterV1(
             @NonNull Context context,
@@ -142,6 +145,11 @@
     }
 
     @Override
+    public void setTitle(CarUiText title) {
+        update(mAdapterState.copy().setTitle(charSequenceToString(title.toString())).build());
+    }
+
+    @Override
     public CharSequence getTitle() {
         return mAdapterState.getTitle();
     }
@@ -157,6 +165,12 @@
     }
 
     @Override
+    public void setSubtitle(CarUiText subtitle) {
+        update(mAdapterState.copy().setSubtitle(
+                charSequenceToString(subtitle.getPreferredText())).build());
+    }
+
+    @Override
     public CharSequence getSubtitle() {
         return mAdapterState.getSubtitle();
     }
@@ -170,7 +184,7 @@
     public void setTabs(@Nullable List<Tab> tabs, int selectedTab) {
         mDeprecatedTabs.clear();
         if (tabs == null || tabs.isEmpty()) {
-            selectedTab = 0;
+            selectedTab = -1;
         } else if (selectedTab < 0 || selectedTab >= tabs.size()) {
             throw new IllegalArgumentException("Tab position is invalid: " + selectedTab);
         }
@@ -304,6 +318,11 @@
     }
 
     @Override
+    public SearchMode getSearchMode() {
+        return mAdapterState.getSearchMode();
+    }
+
+    @Override
     public void setNavButtonMode(Toolbar.NavButtonMode style) {
         switch (style) {
             case BACK:
@@ -328,36 +347,26 @@
     }
 
     @Override
-    public Toolbar.NavButtonMode getNavButtonMode() {
-        NavButtonMode mode = mAdapterState.getNavButtonMode();
-        switch (mode) {
-            case BACK:
-                return Toolbar.NavButtonMode.BACK;
-            case DOWN:
-                return Toolbar.NavButtonMode.DOWN;
-            case CLOSE:
-                return Toolbar.NavButtonMode.CLOSE;
-            case DISABLED:
-            default:
-                return Toolbar.NavButtonMode.DISABLED;
-        }
+    public NavButtonMode getNavButtonMode() {
+        return mAdapterState.getNavButtonMode();
     }
 
     @Override
     public void setBackgroundShown(boolean shown) {
-        Log.w(TAG, "Unsupported operation setBackgroundShown() called, ignoring");
+        mBackgroundShown = shown;
+        mOemToolbar.setBackgroundShown(shown);
     }
 
     @Override
     public boolean getBackgroundShown() {
-        return true;
+        return mBackgroundShown;
     }
 
     @Override
     public void setMenuItems(@Nullable List<MenuItem> items) {
         mClientMenuItems = items;
         update(mAdapterState.copy()
-                .setMenuItems(convertList(items, MenuItemAdapterV1::new))
+                .setMenuItems(convertList(items, item -> new MenuItemAdapterV1(this, item)))
                 .build());
     }
 
@@ -419,13 +428,13 @@
 
     /**
      * This method takes a new {@link ToolbarAdapterState} and compares it to the current
-     * {@link #mAdapterState}. It then sends any differences it detects to the shared library
-     * toolbar.
-     *
-     * This is also the core of the logic that adapts from the client's toolbar interface to
-     * the OEM apis toolbar interface. For example, when you are in the HOME state and add tabs,
-     * it will call setTitle(null) on the shared library toolbar. This is because the client
-     * interface
+     * {@link ToolbarAdapterState}. It then sends any differences it detects to the plugin toolbar.
+     * <p>
+     * This is also the core of the logic that adapts from the client's toolbar interface to the OEM
+     * apis toolbar interface. For example, when you are in the HOME state and add tabs, it will
+     * call setTitle(null) on the plugin toolbar. This is because the plugin interface doesn't have
+     * a setState(), and the title is expected to not be present when there are tabs and a HOME
+     * state.
      */
     private void update(ToolbarAdapterState newAdapterState) {
         ToolbarAdapterState oldAdapterState = mAdapterState;
@@ -476,16 +485,16 @@
         boolean losingTabs = !newAdapterState.hasTabs() && oldAdapterState.hasTabs();
         if (gainingTabs) {
             mOemToolbar.setTabs(newAdapterState.getTabs()
-                    .stream()
-                    .map(TabAdapterV1::getSharedLibraryTab)
-                    .collect(toList()),
+                            .stream()
+                            .map(TabAdapterV1::getPluginTab)
+                            .collect(toList()),
                     newAdapterState.getSelectedTab());
         } else if (losingTabs) {
             mOemToolbar.setTabs(Collections.emptyList(), -1);
         } else if (newAdapterState.hasTabs() && newAdapterState.getTabsDirty()) {
             mOemToolbar.setTabs(newAdapterState.getTabs()
                             .stream()
-                            .map(TabAdapterV1::getSharedLibraryTab)
+                            .map(TabAdapterV1::getPluginTab)
                             .collect(toList()),
                     newAdapterState.getSelectedTab());
         } else if (newAdapterState.hasTabs()
@@ -499,6 +508,13 @@
         }
     }
 
+    /**
+     * Called by {@link MenuItemAdapterV1} whenever a MenuItem changes.
+     */
+    public void updateMenuItems() {
+        mOemToolbar.setMenuItems(mAdapterState.getShownMenuItems());
+    }
+
     @Override
     public State getState() {
         return mAdapterState.getState();
@@ -768,17 +784,24 @@
                     && !getTabs().isEmpty();
         }
 
-        private List<MenuItemAdapterV1> getShownMenuItems() {
+        private List<MenuItemOEMV1> getShownMenuItems() {
             SearchMode searchMode = getSearchMode();
-            if (searchMode == SearchMode.EDIT) {
-                return mShowMenuItemsWhileSearching ? mMenuItems : Collections.emptyList();
+            Stream<MenuItemAdapterV1> stream = mMenuItems.stream();
+            if (searchMode == SearchMode.EDIT && !mShowMenuItemsWhileSearching) {
+                stream = Stream.empty();
             } else if (searchMode == SearchMode.SEARCH) {
-                return mShowMenuItemsWhileSearching
-                        ? mMenuItems.stream().filter(i -> !i.isSearch()).collect(toList())
-                        : Collections.emptyList();
-            } else {
-                return mMenuItems;
+                if (mShowMenuItemsWhileSearching) {
+                    stream = mMenuItems.stream()
+                            .filter(item -> !item.getClientMenuItem().isSearch());
+                } else {
+                    stream = Stream.empty();
+                }
             }
+
+            return Collections.unmodifiableList(stream
+                    .filter(MenuItemAdapterV1::isVisible)
+                    .map(MenuItemAdapterV1::getPluginMenuItem)
+                    .collect(toList()));
         }
 
         private SearchMode getSearchMode() {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
index 9191239..8cd35f9 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/toolbar/ToolbarControllerImpl.java
@@ -20,21 +20,21 @@
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
+import static com.android.car.ui.core.CarUi.TARGET_API_R;
 import static com.android.car.ui.utils.CarUiUtils.findViewByRefId;
 import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
 
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.text.PrecomputedText;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
@@ -44,14 +44,15 @@
 import androidx.core.content.ContextCompat;
 
 import com.android.car.ui.AlertDialogBuilder;
+import com.android.car.ui.CarUiText;
 import com.android.car.ui.R;
 import com.android.car.ui.imewidescreen.CarUiImeSearchListItem;
 import com.android.car.ui.recyclerview.CarUiContentListItem;
 import com.android.car.ui.recyclerview.CarUiListItem;
 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
 import com.android.car.ui.utils.CarUiUtils;
+import com.android.car.ui.widget.CarUiTextView;
 
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -64,10 +65,11 @@
 import java.util.function.Supplier;
 
 /**
- * The implementation of {@link ToolbarController}. This class takes a ViewGroup, and looks
- * in the ViewGroup to find all the toolbar-related views to control.
+ * The implementation of {@link ToolbarController}. This class takes a ViewGroup, and looks in the
+ * ViewGroup to find all the toolbar-related views to control.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(TARGET_API_R)
 public final class ToolbarControllerImpl implements ToolbarController {
     private static final String TAG = "CarUiToolbarController";
 
@@ -77,12 +79,12 @@
     private ImageView mLogoInNavIconSpace;
     private ViewGroup mNavIconContainer;
     private ViewGroup mTitleContainer;
-    private TextView mTitle;
+    private CarUiTextView mTitle;
     @NonNull
-    private CharSequence mTitleText = "";
-    private TextView mSubtitle;
+    private CarUiText mTitleText = new CarUiText.Builder("").build();
+    private CarUiTextView mSubtitle;
     @NonNull
-    private CharSequence mSubtitleText = "";
+    private CarUiText mSubtitleText = new CarUiText.Builder("").build();
     private ImageView mTitleLogo;
     private ViewGroup mTitleLogoContainer;
     private TabLayout mTabLayout;
@@ -181,6 +183,7 @@
         setBackgroundShown(true);
 
         mOverflowAdapter = new CarUiListItemAdapter(mUiOverflowItems);
+        update();
     }
 
     private Context getContext() {
@@ -212,39 +215,22 @@
      */
     @Override
     public void setTitle(CharSequence title) {
-        mTitleText = title == null ? "" : title;
-        asyncSetText(mTitle, mTitleText, Runnable::run);
+        mTitleText = title == null ? new CarUiText.Builder("").build() : new CarUiText.Builder(
+                title).build();
+        mTitle.setText(mTitleText);
         update();
     }
 
-    private void asyncSetText(TextView textView, @NonNull CharSequence title, Executor bgExecutor) {
-        // construct precompute related parameters using the TextView that we will set the text on.
-        PrecomputedText.Params params = textView.getTextMetricsParams();
-        WeakReference<TextView> textViewRef = new WeakReference<>(textView);
-        bgExecutor.execute(() -> {
-            // background thread
-            TextView tv = textViewRef.get();
-            if (tv == null) {
-                return;
-            }
-            PrecomputedText precomputedText = PrecomputedText.create(title, params);
-            tv.post(() -> {
-                // UI thread
-                TextView tvUi = textViewRef.get();
-                if (tvUi == null) return;
-                try {
-                    tvUi.setTextMetricsParams(precomputedText.getParams());
-                    tvUi.setText(precomputedText);
-                } catch (IllegalArgumentException e) {
-                    tvUi.setText(title);
-                }
-            });
-        });
+    @Override
+    public void setTitle(CarUiText title) {
+        mTitleText = title;
+        mTitle.setText(mTitleText);
+        update();
     }
 
     @Override
     public CharSequence getTitle() {
-        return mTitleText;
+        return mTitleText.getPreferredText();
     }
 
     /**
@@ -264,14 +250,22 @@
      */
     @Override
     public void setSubtitle(CharSequence subTitle) {
-        mSubtitleText = subTitle == null ? "" : subTitle;
-        asyncSetText(mSubtitle, mSubtitleText, Runnable::run);
+        mSubtitleText = subTitle == null ? new CarUiText.Builder("").build()
+                : new CarUiText.Builder(subTitle).build();
+        mSubtitle.setText(mSubtitleText);
+        update();
+    }
+
+    @Override
+    public void setSubtitle(CarUiText text) {
+        mSubtitleText = text;
+        mSubtitle.setText(mSubtitleText);
         update();
     }
 
     @Override
     public CharSequence getSubtitle() {
-        return mSubtitleText;
+        return mSubtitleText.getPreferredText();
     }
 
     @Override
@@ -352,8 +346,7 @@
     }
 
     /**
-     * Selects a tab added to this toolbar. See
-     * {@link #addTab(TabLayout.Tab)}.
+     * Selects a tab added to this toolbar. See {@link #addTab(TabLayout.Tab)}.
      */
     @Override
     public void selectTab(int position) {
@@ -526,22 +519,8 @@
      * Gets the {@link Toolbar.NavButtonMode}
      */
     @Override
-    public Toolbar.NavButtonMode getNavButtonMode() {
-        if (mStateSet && mNavButtonMode == NavButtonMode.DISABLED
-                && mState != Toolbar.State.HOME) {
-            return Toolbar.NavButtonMode.BACK;
-        }
-        switch (mNavButtonMode) {
-            case BACK:
-                return Toolbar.NavButtonMode.BACK;
-            case DOWN:
-                return Toolbar.NavButtonMode.DOWN;
-            case CLOSE:
-                return Toolbar.NavButtonMode.CLOSE;
-            case DISABLED:
-            default:
-                return Toolbar.NavButtonMode.DISABLED;
-        }
+    public NavButtonMode getNavButtonMode() {
+        return mNavButtonMode;
     }
 
     /**
@@ -651,7 +630,8 @@
      * wasn't called), nothing will happen the second time, even if the MenuItems were changed.
      *
      * <p>The XML file must have one <MenuItems> tag, with a variable number of <MenuItem>
-     * child tags. See CarUiToolbarMenuItem in CarUi's attrs.xml for a list of available attributes.
+     * child tags. See CarUiToolbarMenuItem in CarUi's attrs.xml for a list of available
+     * attributes.
      * <p>
      * Example:
      * <pre>
@@ -836,6 +816,11 @@
         }
     }
 
+    @Override
+    public SearchMode getSearchMode() {
+        return mSearchMode;
+    }
+
     private void update() {
         // Start by removing mState/mStateSet from the equation by incorporating them into other
         // variables.
@@ -992,8 +977,7 @@
      *
      * <p>Note: Apps can only call this method if the package name is allowed via OEM to render
      * their view.  To check if the application have the permission to do so or not first call
-     * {@link SearchCapabilities#canShowSearchResultsView()}. If the app is not allowed this
-     * method
+     * {@link SearchCapabilities#canShowSearchResultsView()}. If the app is not allowed this method
      * will throw an {@link IllegalStateException}
      *
      * @param view to be added in the container.
@@ -1015,10 +999,10 @@
     }
 
     /**
-     * Sets list of search item {@link CarUiListItem} to be displayed in the IMS
-     * template. This method should be called when system is running in a wide screen mode. Apps
-     * can check that by using {@link SearchCapabilities#canShowSearchResultItems()}
-     * Else, this method will throw an {@link IllegalStateException}
+     * Sets list of search item {@link CarUiListItem} to be displayed in the IMS template. This
+     * method should be called when system is running in a wide screen mode. Apps can check that by
+     * using {@link SearchCapabilities#canShowSearchResultItems()} Else, this method will throw an
+     * {@link IllegalStateException}
      */
     @Override
     public void setSearchResultItems(List<? extends CarUiImeSearchListItem> searchItems) {
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
index 202617b..54d22fa 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
@@ -15,6 +15,10 @@
  */
 package com.android.car.ui.utils;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -51,6 +55,7 @@
  * Collection of utility methods
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public final class CarUiUtils {
 
     private static final String TAG = "CarUiUtils";
@@ -94,6 +99,20 @@
     }
 
     /**
+     * Gets the boolean value of an Attribute from an {@link Activity Activity's}
+     * {@link android.content.res.Resources.Theme}.
+     */
+    public static boolean getThemeBoolean(Activity activity, int attr) {
+        TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
+
+        try {
+            return a.getBoolean(0, false);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    /**
      * Gets the {@link Activity} for a certain {@link Context}.
      *
      * <p>It is possible the Context is not associated with an Activity, in which case
@@ -122,6 +141,7 @@
      */
     @Nullable
     @UiThread
+    @SuppressWarnings("TypeParameterUnusedInFormals")
     public static <T extends View> T findViewByRefId(@NonNull View root, @IdRes int id) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             return root.findViewById(id);
@@ -148,6 +168,7 @@
      */
     @NonNull
     @UiThread
+    @SuppressWarnings("TypeParameterUnusedInFormals")
     public static <T extends View> T requireViewByRefId(@NonNull View root, @IdRes int id) {
         T view = findViewByRefId(root, id);
         if (view == null) {
@@ -205,6 +226,7 @@
     }
 
     @Nullable
+    @SuppressLint("PrivateApi")
     private static String readSystemProperty(String propertyName) {
         Class<?> systemPropertiesClass;
         try {
@@ -308,29 +330,68 @@
      * {@link DrawableStateView#setExtraDrawableState(int[], int[])}
      */
     public static void makeAllViewsUxRestricted(@Nullable View view, boolean restricted) {
-        if (view instanceof DrawableStateView) {
-            if (sRestrictedState == null) {
-                int androidStateUxRestricted = view.getResources()
-                        .getIdentifier("state_ux_restricted", "attr", "android");
-
-                if (androidStateUxRestricted == 0) {
-                    sRestrictedState = new int[] { R.attr.state_ux_restricted };
-                } else {
-                    sRestrictedState = new int[] {
-                            R.attr.state_ux_restricted,
-                            androidStateUxRestricted
-                    };
-                }
-            }
-
-            ((DrawableStateView) view).setExtraDrawableState(
-                    restricted ? sRestrictedState : null, null);
+        if (view == null) {
+            return;
         }
+        initializeRestrictedState(view);
+        applyStatesToAllViews(view, restricted ? sRestrictedState : null, null);
+    }
 
+    /**
+     * Traverses the view hierarchy, and whenever it sees a {@link DrawableStateView}, adds
+     * the relevant state_enabled and state_ux_restricted to the view.
+     *
+     * Note that this will remove any other drawable states added by other calls to
+     * {@link DrawableStateView#setExtraDrawableState(int[], int[])}
+     */
+    public static void makeAllViewsEnabledAndUxRestricted(@Nullable View view, boolean enabled,
+            boolean restricted) {
+        if (view == null) {
+            return;
+        }
+        initializeRestrictedState(view);
+        int[] statesToAdd = null;
+        if (enabled) {
+            if (restricted) {
+                statesToAdd = new int[sRestrictedState.length + 1];
+                statesToAdd[0] = android.R.attr.state_enabled;
+                System.arraycopy(sRestrictedState, 0, statesToAdd, 1, sRestrictedState.length);
+            } else {
+                statesToAdd = new int[] {android.R.attr.state_enabled};
+            }
+        } else if (restricted) {
+            statesToAdd = sRestrictedState;
+        }
+        int[] statesToRemove = enabled ? null : new int[] {android.R.attr.state_enabled};
+        applyStatesToAllViews(view, statesToAdd, statesToRemove);
+    }
+
+    private static void initializeRestrictedState(@NonNull View view) {
+        if (sRestrictedState != null) {
+            return;
+        }
+        int androidStateUxRestricted = view.getResources()
+                .getIdentifier("state_ux_restricted", "attr", "android");
+
+        if (androidStateUxRestricted == 0) {
+            sRestrictedState = new int[] { R.attr.state_ux_restricted };
+        } else {
+            sRestrictedState = new int[] {
+                    R.attr.state_ux_restricted,
+                    androidStateUxRestricted
+            };
+        }
+    }
+
+    private static void applyStatesToAllViews(@NonNull View view, int[] statesToAdd,
+            int[] statesToRemove) {
+        if (view instanceof DrawableStateView) {
+            ((DrawableStateView) view).setExtraDrawableState(statesToAdd, statesToRemove);
+        }
         if (view instanceof ViewGroup) {
             ViewGroup vg = (ViewGroup) view;
             for (int i = 0; i < vg.getChildCount(); i++) {
-                makeAllViewsUxRestricted(vg.getChildAt(i), restricted);
+                applyStatesToAllViews(vg.getChildAt(i), statesToAdd, statesToRemove);
             }
         }
     }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
index 19f2fbb..fcf5cd5 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUxRestrictionsUtil.java
@@ -39,8 +39,8 @@
  * 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}.
+ * CarUxRestrictionsManager} at a time, as documented in
+ * {@link CarUxRestrictionsManager#registerListener}.
  */
 public class CarUxRestrictionsUtil {
     private static final String TAG = "CarUxRestrictionsUtil";
@@ -50,63 +50,45 @@
 
     private final Set<OnUxRestrictionsChangedListener> mObservers =
             Collections.newSetFromMap(new WeakHashMap<>());
+    private final CarUxRestrictionsManager.OnUxRestrictionsChangedListener mListener =
+            (carUxRestrictions) -> {
+                if (carUxRestrictions == null) {
+                    mCarUxRestrictions = getDefaultRestrictions();
+                } else {
+                    mCarUxRestrictions = carUxRestrictions;
+                }
+
+                for (OnUxRestrictionsChangedListener observer : mObservers) {
+                    observer.onRestrictionsChanged(mCarUxRestrictions);
+                }
+            };
     private static CarUxRestrictionsUtil sInstance = null;
 
     private CarUxRestrictionsUtil(Context context) {
-        CarUxRestrictionsManager.OnUxRestrictionsChangedListener listener =
-                (carUxRestrictions) -> {
-                    if (carUxRestrictions == null) {
-                        mCarUxRestrictions = getDefaultRestrictions();
-                    } else {
-                        mCarUxRestrictions = carUxRestrictions;
-                    }
-
-                    for (OnUxRestrictionsChangedListener observer : mObservers) {
-                        observer.onRestrictionsChanged(mCarUxRestrictions);
-                    }
-                };
-
         try {
-            // copybara:strip_begin
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                 Car.createCar(context.getApplicationContext(), null,
-                    Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
-                    (Car car, boolean ready) -> {
-                        if (ready) {
-                            CarUxRestrictionsManager carUxRestrictionsManager =
-                                (CarUxRestrictionsManager) car.getCarManager(
-                                    Car.CAR_UX_RESTRICTION_SERVICE);
-                            carUxRestrictionsManager.registerListener(listener);
-                            listener.onUxRestrictionsChanged(
-                                carUxRestrictionsManager.getCurrentCarUxRestrictions());
-                        } else {
-                            Log.w(TAG, "Car service disconnected, assuming fully restricted uxr");
-                            listener.onUxRestrictionsChanged(null);
-                        }
-                    });
+                        Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                        (Car car, boolean ready) -> {
+                            if (ready) {
+                                registerCarUxRestrictionsListener(car, mListener);
+                            } else {
+                                Log.w(TAG, "Car service disconnected, assuming fully"
+                                        + " restricted uxr");
+                                mListener.onUxRestrictionsChanged(null);
+                            }
+                        });
             } else {
-                // copybara:strip_end
-                Car carApi = Car.createCar(context.getApplicationContext());
-
-                try {
-                    CarUxRestrictionsManager carUxRestrictionsManager =
-                        (CarUxRestrictionsManager) carApi.getCarManager(
-                            Car.CAR_UX_RESTRICTION_SERVICE);
-                    carUxRestrictionsManager.registerListener(listener);
-                    listener.onUxRestrictionsChanged(
-                        carUxRestrictionsManager.getCurrentCarUxRestrictions());
-                } catch (NullPointerException e) {
-                    Log.e(TAG, "Car not connected", e);
-                    // mCarUxRestrictions will be the default
-                }
-                // copybara:strip_begin
+                Car car = Car.createCar(context.getApplicationContext());
+                registerCarUxRestrictionsListener(car, mListener);
             }
-            // copybara:strip_end
-        } catch (SecurityException e) {
+        } catch (RuntimeException e) {
+            // Can't catch more specific exception, because
+            // Car class literally throws RuntimeException or SecurityException on error.
             Log.w(TAG, "Unable to connect to car service, assuming unrestricted", e);
-            listener.onUxRestrictionsChanged(new CarUxRestrictions.Builder(
-                false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE, 0)
-                .build());
+            mListener.onUxRestrictionsChanged(new CarUxRestrictions.Builder(
+                    false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE, 0)
+                    .build());
         }
     }
 
@@ -117,13 +99,19 @@
                 .build();
     }
 
-    /** Listener interface used to update clients on UxRestrictions changes */
+    /**
+     * Listener interface used to update clients on UxRestrictions changes
+     */
     public interface OnUxRestrictionsChangedListener {
-        /** Called when CarUxRestrictions changes */
+        /**
+         * Called when CarUxRestrictions changes
+         */
         void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions);
     }
 
-    /** Returns the singleton sInstance of this class */
+    /**
+     * Returns the singleton sInstance of this class
+     */
     @NonNull
     public static CarUxRestrictionsUtil getInstance(Context context) {
         if (sInstance == null) {
@@ -135,15 +123,17 @@
 
     /**
      * Registers a listener on this class for updates to CarUxRestrictions. Multiple listeners may
-     * be registered. Note that this class will only hold a weak reference to the listener, you
-     * must maintain a strong reference to it elsewhere.
+     * be registered. Note that this class will only hold a weak reference to the listener, you must
+     * maintain a strong reference to it elsewhere.
      */
     public void register(OnUxRestrictionsChangedListener listener) {
         mObservers.add(listener);
         listener.onRestrictionsChanged(mCarUxRestrictions);
     }
 
-    /** Unregisters a registered listener */
+    /**
+     * Unregisters a registered listener
+     */
     public void unregister(OnUxRestrictionsChangedListener listener) {
         mObservers.remove(listener);
     }
@@ -183,9 +173,28 @@
         return str;
     }
 
-    /** Sets car UX restrictions. Only used for testing. */
+    /**
+     * Sets car UX restrictions. Only used for testing.
+     */
     @VisibleForTesting
     public void setUxRestrictions(CarUxRestrictions carUxRestrictions) {
         mCarUxRestrictions = carUxRestrictions;
+        mListener.onUxRestrictionsChanged(mCarUxRestrictions);
+    }
+
+    private static void registerCarUxRestrictionsListener(
+            @NonNull Car car,
+            @NonNull CarUxRestrictionsManager.OnUxRestrictionsChangedListener listener) {
+        try {
+            CarUxRestrictionsManager carUxRestrictionsManager =
+                    (CarUxRestrictionsManager) car.getCarManager(
+                            Car.CAR_UX_RESTRICTION_SERVICE);
+            carUxRestrictionsManager.registerListener(listener);
+            listener.onUxRestrictionsChanged(
+                    carUxRestrictionsManager.getCurrentCarUxRestrictions());
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Car not connected", e);
+            // mCarUxRestrictions will be the default
+        }
     }
 }
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java
index bf7fcb1..d8662eb 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateButton.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.Button;
@@ -25,6 +26,7 @@
  * A {@link Button} that implements {@link DrawableStateView}, for allowing additional states
  * such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateButton extends Button implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateImageView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateImageView.java
index 511da66..a9ecab0 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateImageView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateImageView.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.ImageView;
@@ -25,6 +26,7 @@
  * A {@link ImageView} that implements {@link DrawableStateView}, for allowing additional states
  * such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateImageView extends ImageView implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateTextView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateTextView.java
index 42324fe..a53ffc4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateTextView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateTextView.java
@@ -15,6 +15,7 @@
  */
 package com.android.car.ui.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.TextView;
@@ -25,6 +26,7 @@
  * A {@link TextView} that implements {@link DrawableStateView}, for allowing additional
  * states such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateTextView extends TextView implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateToggleButton.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateToggleButton.java
index b48270f..b99954f 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateToggleButton.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateToggleButton.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.ToggleButton;
@@ -26,6 +27,7 @@
  * A {@link ToggleButton} that implements {@link DrawableStateView}, for allowing additional states
  * such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateToggleButton extends ToggleButton implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateUtil.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateUtil.java
index 09906cb..31ea86b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateUtil.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/uxr/DrawableStateUtil.java
@@ -15,6 +15,9 @@
  */
 package com.android.car.ui.uxr;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
+import android.annotation.TargetApi;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -29,6 +32,7 @@
  * {@link DrawableStateView#setExtraDrawableState(int[], int[])} methods to this object.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 class DrawableStateUtil implements DrawableStateView {
 
     private final View mView;
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextView.java
index 8aa308e..721e7b4 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextView.java
@@ -16,17 +16,21 @@
 
 package com.android.car.ui.widget;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
 
 import com.android.car.ui.CarUiLayoutInflaterFactory;
 import com.android.car.ui.CarUiText;
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryFactorySingleton;
+import com.android.car.ui.pluginsupport.PluginFactorySingleton;
 
 import java.util.List;
 
@@ -35,12 +39,12 @@
  * CarUiText}.
  */
 @SuppressLint("AppCompatCustomView")
-public abstract class CarUiTextView extends TextView {
-
+@TargetApi(MIN_TARGET_API)
+public abstract class CarUiTextView extends AppCompatTextView {
     /**
      * Creates a CarUiTextView.
      *
-     * Most of the time, you should prefer creating a CarUiButton with a {@code <CarUiTextView>}
+     * Most of the time, you should prefer creating a CarUiTextView with a {@code <CarUiTextView>}
      * tag in your layout file. This is only for if you need to create a CarUiTextView in java code.
      * The CarUiTextView xml tag is enabled by the usage of {@link CarUiLayoutInflaterFactory}.
      */
@@ -51,32 +55,26 @@
     /**
      * Creates a CarUiTextView.
      *
-     * Most of the time, you should prefer creating a CarUiButton with a {@code <CarUiTextView>}
+     * Most of the time, you should prefer creating a CarUiTextView with a {@code <CarUiTextView>}
      * tag in your layout file. This is only for if you need to create a CarUiTextView in java code.
      * The CarUiTextView xml tag is enabled by the usage of {@link CarUiLayoutInflaterFactory}.
      */
-    static CarUiTextView create(@NonNull Context context, @Nullable AttributeSet attrs) {
-        return SharedLibraryFactorySingleton.get(context)
-                .createTextView(context, attrs);
+    public static CarUiTextView create(@NonNull Context context, @Nullable AttributeSet attrs) {
+        return PluginFactorySingleton.get(context).createTextView(context, attrs);
     }
 
     public CarUiTextView(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public CarUiTextView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, android.R.attr.textViewStyle);
     }
 
     public CarUiTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
-    public CarUiTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     /**
      * Set text to display.
      *
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextViewImpl.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextViewImpl.java
index a5b8eac..1b15d3b 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextViewImpl.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/widget/CarUiTextViewImpl.java
@@ -16,10 +16,14 @@
 
 package com.android.car.ui.widget;
 
+import static com.android.car.ui.core.CarUi.MIN_TARGET_API;
+
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -32,14 +36,17 @@
 
 import com.android.car.ui.CarUiText;
 
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.List;
 import java.util.Scanner;
+import java.util.concurrent.Executor;
 
 /**
  * Extension of {@link TextView} that supports {@link CarUiText}.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
+@TargetApi(MIN_TARGET_API)
 public final class CarUiTextViewImpl extends CarUiTextView {
 
     @NonNull
@@ -58,11 +65,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public CarUiTextViewImpl(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-                             int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     /**
      * Set text to display.
      *
@@ -72,10 +74,7 @@
     @Override
     public void setText(@NonNull List<CarUiText> textList) {
         mText = requireNonNull(textList);
-        if (mOneShotPreDrawListener == null) {
-            mOneShotPreDrawListener = OneShotPreDrawListener.add(this, this::updateText);
-        }
-        setText(CarUiText.combineMultiLine(textList));
+        asyncSetText(CarUiText.combineMultiLine(textList), true, Runnable::run);
     }
 
     /**
@@ -84,10 +83,7 @@
     @Override
     public void setText(@NonNull CarUiText text) {
         mText = Collections.singletonList(requireNonNull(text));
-        if (mOneShotPreDrawListener == null) {
-            mOneShotPreDrawListener = OneShotPreDrawListener.add(this, this::updateText);
-        }
-        setText(text.getPreferredText());
+        asyncSetText(text.getPreferredText(), true, Runnable::run);
     }
 
     private void updateText() {
@@ -116,7 +112,38 @@
             delimiter = "\n";
         }
 
-        setText(builder);
+        asyncSetText(builder, false, Runnable::run);
+    }
+
+    private void asyncSetText(@NonNull CharSequence text, boolean requiresUpdate,
+            @NonNull Executor bgExecutor) {
+        // construct precompute related parameters using the TextView that we will set the text on.
+        PrecomputedText.Params params = getTextMetricsParams();
+        WeakReference<TextView> textViewRef = new WeakReference<>(this);
+        bgExecutor.execute(() -> {
+            // background thread
+            TextView tv = textViewRef.get();
+            if (tv == null) {
+                return;
+            }
+            PrecomputedText precomputedText = PrecomputedText.create(text, params);
+            tv.post(() -> {
+                // UI thread
+                TextView tvUi = textViewRef.get();
+                if (tvUi == null) return;
+                try {
+                    tvUi.setTextMetricsParams(precomputedText.getParams());
+                    tvUi.setText(precomputedText);
+                } catch (IllegalArgumentException e) {
+                    tvUi.setText(text);
+                }
+
+                if (requiresUpdate && mOneShotPreDrawListener == null) {
+                    mOneShotPreDrawListener = OneShotPreDrawListener.add(this, this::updateText);
+                }
+
+            });
+        });
     }
 
     private CharSequence getBestVariant(CarUiText text) {
diff --git a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
index d65d18f..4682c96 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/overlayable.xml
@@ -15,20 +15,66 @@
 <!-- THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY. -->
 <resources>
   <overlayable name="car-ui-lib">
-    <policy type="public">
+    <policy type="odm|oem|product|signature|system|vendor">
       <item type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
       <item type="attr" name="CarUiToolbarStyle"/>
+      <item type="attr" name="actionEnabled"/>
+      <item type="attr" name="actionShown"/>
+      <item type="attr" name="activatable"/>
+      <item type="attr" name="activated"/>
+      <item type="attr" name="barrierAllowsGoneWidgets"/>
       <item type="attr" name="barrierDirection"/>
+      <item type="attr" name="barrierMargin"/>
+      <item type="attr" name="carUiActivity"/>
+      <item type="attr" name="carUiBaseLayout"/>
+      <item type="attr" name="carUiClickableWhileDisabled"/>
+      <item type="attr" name="carUiIcon"/>
+      <item type="attr" name="carUiLayout"/>
       <item type="attr" name="carUiPreferenceStyle"/>
       <item type="attr" name="carUiRecyclerViewStyle"/>
+      <item type="attr" name="carUiShowChevron"/>
+      <item type="attr" name="carUiSize"/>
+      <item type="attr" name="carUiToolbar"/>
+      <item type="attr" name="car_ui_ux_restricted"/>
       <item type="attr" name="chainUseRtl"/>
+      <item type="attr" name="checkable"/>
+      <item type="attr" name="checked"/>
       <item type="attr" name="constraintSet"/>
       <item type="attr" name="constraint_referenced_ids"/>
+      <item type="attr" name="displayBehavior"/>
+      <item type="attr" name="enableDivider"/>
+      <item type="attr" name="flow_firstHorizontalBias"/>
+      <item type="attr" name="flow_firstHorizontalStyle"/>
+      <item type="attr" name="flow_firstVerticalBias"/>
+      <item type="attr" name="flow_firstVerticalStyle"/>
+      <item type="attr" name="flow_horizontalAlign"/>
+      <item type="attr" name="flow_horizontalBias"/>
+      <item type="attr" name="flow_horizontalGap"/>
+      <item type="attr" name="flow_horizontalStyle"/>
+      <item type="attr" name="flow_lastHorizontalBias"/>
+      <item type="attr" name="flow_lastHorizontalStyle"/>
+      <item type="attr" name="flow_lastVerticalBias"/>
+      <item type="attr" name="flow_lastVerticalStyle"/>
+      <item type="attr" name="flow_maxElementsWrap"/>
+      <item type="attr" name="flow_verticalAlign"/>
+      <item type="attr" name="flow_verticalBias"/>
+      <item type="attr" name="flow_verticalGap"/>
+      <item type="attr" name="flow_verticalStyle"/>
+      <item type="attr" name="flow_wrapMode"/>
+      <item type="attr" name="id"/>
+      <item type="attr" name="layoutDescription"/>
+      <item type="attr" name="layoutManager"/>
+      <item type="attr" name="layoutStyle"/>
+      <item type="attr" name="layout_constrainedHeight"/>
+      <item type="attr" name="layout_constrainedWidth"/>
       <item type="attr" name="layout_constraintBaseline_creator"/>
       <item type="attr" name="layout_constraintBaseline_toBaselineOf"/>
       <item type="attr" name="layout_constraintBottom_creator"/>
       <item type="attr" name="layout_constraintBottom_toBottomOf"/>
       <item type="attr" name="layout_constraintBottom_toTopOf"/>
+      <item type="attr" name="layout_constraintCircle"/>
+      <item type="attr" name="layout_constraintCircleAngle"/>
+      <item type="attr" name="layout_constraintCircleRadius"/>
       <item type="attr" name="layout_constraintDimensionRatio"/>
       <item type="attr" name="layout_constraintEnd_toEndOf"/>
       <item type="attr" name="layout_constraintEnd_toStartOf"/>
@@ -50,6 +96,7 @@
       <item type="attr" name="layout_constraintRight_toRightOf"/>
       <item type="attr" name="layout_constraintStart_toEndOf"/>
       <item type="attr" name="layout_constraintStart_toStartOf"/>
+      <item type="attr" name="layout_constraintTag"/>
       <item type="attr" name="layout_constraintTop_creator"/>
       <item type="attr" name="layout_constraintTop_toBottomOf"/>
       <item type="attr" name="layout_constraintTop_toTopOf"/>
@@ -69,9 +116,29 @@
       <item type="attr" name="layout_goneMarginStart"/>
       <item type="attr" name="layout_goneMarginTop"/>
       <item type="attr" name="layout_optimizationLevel"/>
+      <item type="attr" name="logo"/>
+      <item type="attr" name="menuItems"/>
+      <item type="attr" name="numOfColumns"/>
+      <item type="attr" name="onClick"/>
       <item type="attr" name="preferenceStyle"/>
+      <item type="attr" name="reverseLayout"/>
+      <item type="attr" name="rotaryScrollEnabled"/>
+      <item type="attr" name="search"/>
+      <item type="attr" name="searchHint"/>
+      <item type="attr" name="secondaryActionIcon"/>
+      <item type="attr" name="secondaryActionStyle"/>
+      <item type="attr" name="secondaryActionText"/>
+      <item type="attr" name="settings"/>
+      <item type="attr" name="showBackground"/>
+      <item type="attr" name="showIconAndTitle"/>
+      <item type="attr" name="showMenuItemsWhileSearching"/>
+      <item type="attr" name="showTabsInSubpage"/>
       <item type="attr" name="state_ux_restricted"/>
+      <item type="attr" name="tinted"/>
       <item type="attr" name="title"/>
+      <item type="attr" name="uxRestrictions"/>
+      <item type="attr" name="visible"/>
+      <item type="attr" name="widgetLayout"/>
       <item type="bool" name="car_ui_alert_dialog_force_dismiss_button"/>
       <item type="bool" name="car_ui_escrow_check_components_automatically"/>
       <item type="bool" name="car_ui_ime_wide_screen_aligned_left"/>
@@ -166,6 +233,10 @@
       <item type="dimen" name="car_ui_list_item_action_divider_width"/>
       <item type="dimen" name="car_ui_list_item_avatar_icon_height"/>
       <item type="dimen" name="car_ui_list_item_avatar_icon_width"/>
+      <item type="dimen" name="car_ui_list_item_check_box_end_inset"/>
+      <item type="dimen" name="car_ui_list_item_check_box_height"/>
+      <item type="dimen" name="car_ui_list_item_check_box_icon_container_width"/>
+      <item type="dimen" name="car_ui_list_item_check_box_start_inset"/>
       <item type="dimen" name="car_ui_list_item_content_icon_height"/>
       <item type="dimen" name="car_ui_list_item_content_icon_width"/>
       <item type="dimen" name="car_ui_list_item_end_inset"/>
@@ -174,6 +245,10 @@
       <item type="dimen" name="car_ui_list_item_height"/>
       <item type="dimen" name="car_ui_list_item_icon_container_width"/>
       <item type="dimen" name="car_ui_list_item_icon_size"/>
+      <item type="dimen" name="car_ui_list_item_radio_button_end_inset"/>
+      <item type="dimen" name="car_ui_list_item_radio_button_height"/>
+      <item type="dimen" name="car_ui_list_item_radio_button_icon_container_width"/>
+      <item type="dimen" name="car_ui_list_item_radio_button_start_inset"/>
       <item type="dimen" name="car_ui_list_item_start_inset"/>
       <item type="dimen" name="car_ui_list_item_supplemental_icon_size"/>
       <item type="dimen" name="car_ui_list_item_text_no_icon_start_margin"/>
@@ -329,10 +404,12 @@
       <item type="id" name="car_ui_list_item_start_guideline"/>
       <item type="id" name="car_ui_list_item_supplemental_icon"/>
       <item type="id" name="car_ui_list_item_switch_widget"/>
+      <item type="id" name="car_ui_list_item_text_container"/>
       <item type="id" name="car_ui_list_item_title"/>
       <item type="id" name="car_ui_list_item_touch_interceptor"/>
       <item type="id" name="car_ui_list_limiting_message"/>
       <item type="id" name="car_ui_preference_container_without_widget"/>
+      <item type="id" name="car_ui_preference_fragment_container"/>
       <item type="id" name="car_ui_recycler_view"/>
       <item type="id" name="car_ui_scroll_bar"/>
       <item type="id" name="car_ui_scrollbar_page_down"/>
@@ -342,6 +419,7 @@
       <item type="id" name="car_ui_second_action_container"/>
       <item type="id" name="car_ui_secondary_action"/>
       <item type="id" name="car_ui_secondary_action_concrete"/>
+      <item type="id" name="car_ui_toolbar"/>
       <item type="id" name="car_ui_toolbar_background"/>
       <item type="id" name="car_ui_toolbar_bottom_guideline"/>
       <item type="id" name="car_ui_toolbar_bottom_styleable"/>
@@ -395,6 +473,7 @@
       <item type="id" name="spinner"/>
       <item type="id" name="textbox"/>
       <item type="id" name="title_template"/>
+      <item type="id" name="toolbar"/>
       <item type="integer" name="car_ui_default_max_string_length"/>
       <item type="integer" name="car_ui_scrollbar_longpress_initial_delay"/>
       <item type="integer" name="car_ui_scrollbar_longpress_repeat_interval"/>
@@ -403,18 +482,21 @@
       <item type="layout" name="car_ui_alert_dialog_title_with_subtitle"/>
       <item type="layout" name="car_ui_base_layout"/>
       <item type="layout" name="car_ui_base_layout_toolbar"/>
+      <item type="layout" name="car_ui_base_layout_toolbar_legacy"/>
       <item type="layout" name="car_ui_header_list_item"/>
       <item type="layout" name="car_ui_ims_wide_screen_input_view"/>
       <item type="layout" name="car_ui_list_item"/>
       <item type="layout" name="car_ui_list_item_compact"/>
       <item type="layout" name="car_ui_list_limiting_message"/>
       <item type="layout" name="car_ui_list_preference"/>
+      <item type="layout" name="car_ui_list_preference_with_toolbar"/>
       <item type="layout" name="car_ui_preference"/>
       <item type="layout" name="car_ui_preference_category"/>
       <item type="layout" name="car_ui_preference_chevron"/>
       <item type="layout" name="car_ui_preference_dialog_edittext"/>
       <item type="layout" name="car_ui_preference_dropdown"/>
       <item type="layout" name="car_ui_preference_fragment"/>
+      <item type="layout" name="car_ui_preference_fragment_with_toolbar"/>
       <item type="layout" name="car_ui_preference_two_action_icon"/>
       <item type="layout" name="car_ui_preference_two_action_switch"/>
       <item type="layout" name="car_ui_preference_two_action_text"/>
@@ -444,6 +526,7 @@
       <item type="string" name="car_ui_ellipsis"/>
       <item type="string" name="car_ui_ime_wide_screen_system_property_name"/>
       <item type="string" name="car_ui_installer_process_name"/>
+      <item type="string" name="car_ui_plugin_package_provider_authority_name"/>
       <item type="string" name="car_ui_preference_switch_off"/>
       <item type="string" name="car_ui_preference_switch_on"/>
       <item type="string" name="car_ui_restricted_while_driving"/>
@@ -451,13 +534,13 @@
       <item type="string" name="car_ui_scrollbar_page_down_button"/>
       <item type="string" name="car_ui_scrollbar_page_up_button"/>
       <item type="string" name="car_ui_scrolling_limited_message"/>
-      <item type="string" name="car_ui_shared_library_package_system_property_name"/>
       <item type="string" name="car_ui_toolbar_default_search_hint"/>
       <item type="string" name="car_ui_toolbar_menu_item_overflow_title"/>
       <item type="string" name="car_ui_toolbar_menu_item_search_title"/>
       <item type="string" name="car_ui_toolbar_menu_item_settings_title"/>
       <item type="string" name="car_ui_toolbar_nav_icon_content_description"/>
       <item type="style" name="CarUiPreferenceTheme"/>
+      <item type="style" name="CarUiPreferenceTheme.WithToolbar"/>
       <item type="style" name="Preference.CarUi"/>
       <item type="style" name="Preference.CarUi.Category"/>
       <item type="style" name="Preference.CarUi.CheckBoxPreference"/>
@@ -477,6 +560,7 @@
       <item type="style" name="Preference.CarUi.SeekBarPreference"/>
       <item type="style" name="Preference.CarUi.SwitchPreference"/>
       <item type="style" name="PreferenceFragment.CarUi"/>
+      <item type="style" name="PreferenceFragment.CarUi.WithToolbar"/>
       <item type="style" name="PreferenceFragmentList.CarUi"/>
       <item type="style" name="TextAppearance.CarUi"/>
       <item type="style" name="TextAppearance.CarUi.AlertDialog.Subtitle"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/removed_resources.xml b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/removed_resources.xml
new file mode 100644
index 0000000..aaa31b3
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res-overlayable/values/removed_resources.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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>
+    <!-- This file is used to keep track of removed resources that used to be in overlayable.xml -->
+    <!-- These resources are considered deprecated, and shouldn't be overlaid in any RROs. -->
+    <item type="id" name="nested_recycler_view_layout"/>
+    <item type="layout" name="car_ui_recycler_view_item"/>
+    <item type="attr" name="carUiRecyclerViewStyle"/>
+    <item type="style" name="Widget.CarUi.CarUiRecyclerView"/>
+
+    <!-- Some resources that exists on rvc but not on sc -->
+    <!-- Rotary resources that are now part of car-rotary-lib can't be added here. Because they're
+      now part of rotary-lib overlayable group and 1 resource can't exists in 2 overlaybale group
+      at the same time. -->
+    <item type="dimen" name="car_ui_list_item_check_box_end_inset"/>
+    <item type="dimen" name="car_ui_list_item_check_box_height"/>
+    <item type="dimen" name="car_ui_list_item_check_box_icon_container_width"/>
+    <item type="dimen" name="car_ui_list_item_check_box_start_inset"/>
+    <item type="dimen" name="car_ui_list_item_radio_button_end_inset"/>
+    <item type="dimen" name="car_ui_list_item_radio_button_height"/>
+    <item type="dimen" name="car_ui_list_item_radio_button_icon_container_width"/>
+    <item type="dimen" name="car_ui_list_item_radio_button_start_inset"/>
+    <item type="id" name="car_ui_list_item_text_container"/>
+    <item type="id" name="car_ui_preference_fragment_container"/>
+    <item type="id" name="car_ui_toolbar"/>
+    <item type="id" name="toolbar"/>
+    <item type="layout" name="car_ui_base_layout_toolbar_legacy"/>
+    <item type="layout" name="car_ui_list_preference_with_toolbar"/>
+    <item type="layout" name="car_ui_preference_fragment_with_toolbar"/>
+    <item type="style" name="CarUiPreferenceTheme.WithToolbar"/>
+    <item type="style" name="PreferenceFragment.CarUi.WithToolbar"/>
+
+</resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/drawable/car_ui_app_styled_view_background.xml b/car-ui-lib/car-ui-lib/src/main/res-private/drawable/car_ui_app_styled_view_background.xml
index d740109..5a22fe3 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-private/drawable/car_ui_app_styled_view_background.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/drawable/car_ui_app_styled_view_background.xml
@@ -14,9 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <solid android:color="@color/car_ui_activity_background_color"/>
-  <stroke android:width="3dp" android:color="#B1BCBE" />
-  <corners android:radius="10dp"/>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+  <solid android:color="#282A2D"/>
+  <corners android:radius="16dp"/>
   <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
 </shape>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/layout-land/car_ui_app_styled_view.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout-land/car_ui_app_styled_view.xml
index 47daee0..79e68b8 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-private/layout-land/car_ui_app_styled_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout-land/car_ui_app_styled_view.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:padding="0dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -48,14 +49,14 @@
                 android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
                 android:tint="@color/car_ui_toolbar_nav_icon_color"
                 android:layout_gravity="center"
-                android:scaleType="fitXY"/>
+                android:scaleType="fitXY"
+                tools:ignore="UseAppTint" />
         </FrameLayout>
     </com.android.car.ui.FocusArea>
-    <com.android.car.ui.recyclerview.CarUiRecyclerView
+    <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/car_ui_app_styled_content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        app:carUiSize="small"
         android:layout_marginLeft="@dimen/car_ui_toolbar_first_row_height"
         app:layout_constraintBottom_toBottomOf="parent"/>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view.xml
index f544e1d..8443783 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view.xml
@@ -16,9 +16,10 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:padding="20dp"
+    android:padding="0dp"
     android:background="@drawable/car_ui_app_styled_view_background">
 
     <!-- When not in touch mode, if we clear focus in current window, Android will re-focus the
@@ -49,14 +50,14 @@
                 android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
                 android:tint="@color/car_ui_toolbar_nav_icon_color"
                 android:layout_gravity="center"
-                android:scaleType="fitXY"/>
+                android:scaleType="fitXY"
+                tools:ignore="UseAppTint" />
         </FrameLayout>
     </com.android.car.ui.FocusArea>
-    <com.android.car.ui.recyclerview.CarUiRecyclerView
+    <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/car_ui_app_styled_content"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        app:carUiSize="small"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/car_ui_focus_area"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view_item.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view_item.xml
index f202a65..c5943bd 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_app_styled_view_item.xml
@@ -14,14 +14,8 @@
   ~ 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"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-
   <FrameLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/car_ui_app_styled_item"
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"/>
-
-</androidx.constraintlayout.widget.ConstraintLayout>
+      android:layout_height="match_parent"/>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_medium.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_medium.xml
index 2140275..83dade8 100644
--- a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_medium.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_medium.xml
@@ -16,76 +16,73 @@
   -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
+    <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/car_ui_recycler_view"
+        android:scrollbars="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:paddingLeft="112dp"
-        android:tag="carUiRecyclerView" />
+        android:tag="RecyclerView" />
 
-    <FrameLayout
-        android:layout_width="wrap_content"
+    <androidx.constraintlayout.widget.ConstraintLayout
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:layout_width="112dp"
         android:layout_height="match_parent"
-        android:layout_gravity="left" >
-        <androidx.constraintlayout.widget.ConstraintLayout
-            xmlns:app="http://schemas.android.com/apk/res-auto"
-            android:layout_width="112dp"
-            android:layout_height="match_parent"
-            android:id="@+id/car_ui_scroll_bar"
-            android:gravity="center">
+        android:id="@+id/car_ui_scroll_bar"
+        android:layout_gravity="left"
+        android:gravity="center">
 
-            <ImageView
-                android:id="@+id/car_ui_scrollbar_page_up"
-                android:layout_width="76dp"
-                android:layout_height="76dp"
-                android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
-                android:contentDescription="Scroll up"
-                android:focusable="false"
-                android:hapticFeedbackEnabled="false"
-                android:src="@drawable/car_ui_recyclerview_ic_up_private"
-                android:scaleType="centerInside"
-                android:layout_marginTop="15dp"
-                app:layout_constraintTop_toTopOf="parent"
-                app:layout_constraintLeft_toLeftOf="parent"
-                app:layout_constraintRight_toRightOf="parent"/>
+        <ImageView
+            android:id="@+id/car_ui_scrollbar_page_up"
+            android:layout_width="76dp"
+            android:layout_height="76dp"
+            android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
+            android:contentDescription="Scroll up"
+            android:focusable="false"
+            android:hapticFeedbackEnabled="false"
+            android:src="@drawable/car_ui_recyclerview_ic_up_private"
+            android:scaleType="centerInside"
+            android:layout_marginTop="15dp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"/>
 
-            <!-- View height is dynamically calculated during layout. -->
-            <View
-                android:id="@+id/car_ui_scrollbar_thumb"
-                android:layout_width="7dp"
-                android:layout_height="0dp"
-                android:layout_gravity="center_horizontal"
-                android:background="@drawable/car_ui_recyclerview_scrollbar_thumb_private"
-                app:layout_constraintTop_toTopOf="@+id/car_ui_scrollbar_track"
-                app:layout_constraintBottom_toBottomOf="@+id/car_ui_scrollbar_track"
-                app:layout_constraintLeft_toLeftOf="parent"
-                app:layout_constraintRight_toRightOf="parent"/>
+        <!-- View height is dynamically calculated during layout. -->
+        <View
+            android:id="@+id/car_ui_scrollbar_thumb"
+            android:layout_width="7dp"
+            android:layout_height="0dp"
+            android:layout_gravity="center_horizontal"
+            android:background="@drawable/car_ui_recyclerview_scrollbar_thumb_private"
+            app:layout_constraintTop_toTopOf="@+id/car_ui_scrollbar_track"
+            app:layout_constraintBottom_toBottomOf="@+id/car_ui_scrollbar_track"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"/>
 
-            <View
-                android:id="@+id/car_ui_scrollbar_track"
-                android:layout_width="0dp"
-                android:layout_height="0dp"
-                android:layout_marginTop="16dp"
-                android:layout_marginBottom="16dp"
-                app:layout_constraintTop_toBottomOf="@+id/car_ui_scrollbar_page_up"
-                app:layout_constraintBottom_toTopOf="@+id/car_ui_scrollbar_page_down"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintEnd_toEndOf="parent"/>
+        <View
+            android:id="@+id/car_ui_scrollbar_track"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            app:layout_constraintTop_toBottomOf="@+id/car_ui_scrollbar_page_up"
+            app:layout_constraintBottom_toTopOf="@+id/car_ui_scrollbar_page_down"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
 
-            <ImageView
-                android:id="@+id/car_ui_scrollbar_page_down"
-                android:layout_width="76dp"
-                android:layout_height="76dp"
-                android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
-                android:contentDescription="Scroll down"
-                android:focusable="false"
-                android:hapticFeedbackEnabled="false"
-                android:src="@drawable/car_ui_recyclerview_ic_down_private"
-                android:scaleType="centerInside"
-                android:layout_marginBottom="15dp"
-                app:layout_constraintBottom_toBottomOf="parent"
-                app:layout_constraintLeft_toLeftOf="parent"
-                app:layout_constraintRight_toRightOf="parent"/>
-        </androidx.constraintlayout.widget.ConstraintLayout>
-    </FrameLayout>
+        <ImageView
+            android:id="@+id/car_ui_scrollbar_page_down"
+            android:layout_width="76dp"
+            android:layout_height="76dp"
+            android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
+            android:contentDescription="Scroll down"
+            android:focusable="false"
+            android:hapticFeedbackEnabled="false"
+            android:src="@drawable/car_ui_recyclerview_ic_down_private"
+            android:scaleType="centerInside"
+            android:layout_marginBottom="15dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintRight_toRightOf="parent"/>
+    </androidx.constraintlayout.widget.ConstraintLayout>
 </merge>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_no_scrollbar.xml
similarity index 61%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_no_scrollbar.xml
index 6a35b43..9a30041 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_no_scrollbar.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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,11 +14,12 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-<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>
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/car_ui_recycler_view"
+        android:scrollbars="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:tag="RecyclerView" />
+</merge>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_only.xml
similarity index 64%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_only.xml
index 6a35b43..91809be 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_only.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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,11 +14,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-<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>
+    <androidx.recyclerview.widget.RecyclerView
+        android:scrollbars="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:tag="RecyclerView"/>
+</merge>
diff --git a/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_small.xml b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_small.xml
new file mode 100644
index 0000000..4cf1933
--- /dev/null
+++ b/car-ui-lib/car-ui-lib/src/main/res-private/layout/car_ui_recycler_view_small.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            android:id="@+id/car_ui_scroll_bar"
+            android:layout_width="112dp"
+            android:layout_height="match_parent"
+            android:layout_weight="0"
+            android:gravity="center">
+
+            <ImageView
+                android:id="@+id/car_ui_scrollbar_page_up"
+                android:layout_width="76dp"
+                android:layout_height="76dp"
+                android:layout_marginTop="15dp"
+                android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
+                android:contentDescription="Scroll up"
+                android:focusable="false"
+                android:hapticFeedbackEnabled="false"
+                android:scaleType="centerInside"
+                android:src="@drawable/car_ui_recyclerview_ic_up_private"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <!-- View height is dynamically calculated during layout. -->
+            <ImageView
+                android:id="@+id/car_ui_scrollbar_page_down"
+                android:layout_width="76dp"
+                android:layout_height="76dp"
+                android:layout_marginBottom="15dp"
+                android:background="@drawable/car_ui_recyclerview_button_ripple_background_private"
+                android:contentDescription="Scroll down"
+                android:focusable="false"
+                android:hapticFeedbackEnabled="false"
+                android:scaleType="centerInside"
+                android:src="@drawable/car_ui_recyclerview_ic_down_private"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent" />
+
+            <View
+                android:id="@+id/car_ui_scrollbar_track"
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_marginBottom="16dp"
+                android:layout_marginTop="16dp"
+                app:layout_constraintBottom_toTopOf="@+id/car_ui_scrollbar_page_down"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@+id/car_ui_scrollbar_page_up" />
+
+            <View
+                android:id="@+id/car_ui_scrollbar_thumb"
+                android:layout_width="7dp"
+                android:layout_height="0dp"
+                android:layout_gravity="center_horizontal"
+                android:background="@drawable/car_ui_recyclerview_scrollbar_thumb_private"
+                app:layout_constraintBottom_toBottomOf="@+id/car_ui_scrollbar_track"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent"
+                app:layout_constraintTop_toTopOf="@+id/car_ui_scrollbar_track" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
+            android:id="@+id/car_ui_recycler_view"
+            android:scrollbars="vertical"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:tag="carUiRecyclerView" />
+    </LinearLayout>
+</merge>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
index 801f236..50e52f6 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item.xml
@@ -25,7 +25,7 @@
 
     <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
     the list item view to easily control the bounds of a background ripple effects. -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -36,7 +36,7 @@
         app:layout_constraintTop_toTopOf="parent" />
 
     <!-- This touch interceptor does not include the action container -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_reduced_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -116,7 +116,7 @@
         app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
 
     <!-- This touch interceptor is sized and positioned to encompass the action container   -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_action_container_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item_compact.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item_compact.xml
index 9d3c3a2..ca2fffb 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item_compact.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_list_item_compact.xml
@@ -25,7 +25,7 @@
 
     <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
     the list item view to easily control the bounds of a background ripple effects. -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -36,7 +36,7 @@
         app:layout_constraintTop_toTopOf="parent" />
 
     <!-- This touch interceptor does not include the action container -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_reduced_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -116,7 +116,7 @@
         app:layout_goneMarginStart="@dimen/car_ui_list_item_text_no_icon_start_margin" />
 
     <!-- This touch interceptor is sized and positioned to encompass the action container   -->
-    <View
+    <com.android.car.ui.SecureView
         android:id="@+id/car_ui_list_item_action_container_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml
index ef60b83..9ef4a59 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference.xml
@@ -22,7 +22,6 @@
     android:background="?android:attr/selectableItemBackground"
     android:clipToPadding="false"
     android:minHeight="?android:attr/listPreferredItemHeightSmall"
-    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:tag="carUiPreference"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart">
 
@@ -44,6 +43,7 @@
         android:layout_centerVertical="true"
         android:layout_marginBottom="@dimen/car_ui_preference_content_margin_bottom"
         android:layout_marginTop="@dimen/car_ui_preference_content_margin_top"
+        android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
         android:layout_toEndOf="@android:id/icon"
         android:layout_toStartOf="@android:id/widget_frame"
         android:orientation="vertical">
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml
index a9f3938..71f09c7 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_category.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="?android:attr/selectableItemBackground"
@@ -36,7 +37,8 @@
         android:layout_marginEnd="@dimen/car_ui_preference_category_icon_margin_end"
         android:layout_marginTop="@dimen/car_ui_preference_content_margin_top"
         android:scaleType="fitCenter"
-        android:tint="@color/car_ui_preference_icon_color"/>
+        android:tint="@color/car_ui_preference_icon_color"
+        tools:ignore="UseAppTint" />
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml
index e3f3158..a96b3b3 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_checkbox.xml
@@ -15,11 +15,16 @@
     limitations under the License.
 -->
 
-<CheckBox
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/checkbox"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:focusable="false"
-    android:clickable="false"
-    android:background="@null"/>
+    android:layout_width="?android:attr/listPreferredItemHeightSmall"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall">
+    <CheckBox
+        android:id="@android:id/checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:focusable="false"
+        android:clickable="false"
+        android:background="@null"/>
+</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml
index e51059f..5d6ffac 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_seekbar.xml
@@ -25,7 +25,7 @@
     android:minHeight="?android:attr/listPreferredItemHeightSmall"
     android:orientation="horizontal">
 
-    <ImageView
+    <com.android.car.ui.uxr.DrawableStateImageView
         android:id="@android:id/icon"
         android:layout_width="@dimen/car_ui_preference_icon_size"
         android:layout_height="@dimen/car_ui_preference_icon_size"
@@ -42,14 +42,14 @@
         android:layout_marginTop="@dimen/car_ui_preference_content_margin_top"
         android:layout_marginBottom="@dimen/car_ui_preference_content_margin_bottom">
 
-        <TextView
+        <com.android.car.ui.uxr.DrawableStateTextView
             android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:textAppearance="@style/TextAppearance.CarUi.PreferenceTitle"/>
 
-        <TextView
+        <com.android.car.ui.uxr.DrawableStateTextView
             android:id="@android:id/summary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml
index 2ac924e..68d8387 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_preference_widget_switch.xml
@@ -15,10 +15,15 @@
     limitations under the License.
 -->
 
-<Switch
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/switch_widget"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:clickable="false"
-    android:focusable="false" />
+    android:layout_width="?android:attr/listPreferredItemHeightSmall"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall">
+    <com.android.car.ui.uxr.DrawableStateSwitch
+        android:id="@android:id/switch_widget"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:clickable="false"
+        android:focusable="false" />
+</FrameLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
index 6e12515..0562edb 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view.xml
@@ -21,8 +21,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:paddingStart="@dimen/car_ui_scrollbar_margin"
-        android:paddingEnd="@dimen/car_ui_scrollbar_margin"
-        android:tag="carUiRecyclerView" />
+        android:paddingEnd="@dimen/car_ui_scrollbar_margin"/>
 
     <FrameLayout
         android:layout_width="wrap_content"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml
index d5e626a..1bb4cc4 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recyclerview_scrollbar.xml
@@ -21,6 +21,7 @@
     android:layout_width="@dimen/car_ui_scrollbar_container_width"
     android:layout_height="match_parent"
     android:id="@+id/car_ui_scroll_bar"
+    android:layout_gravity="left"
     android:gravity="center">
 
     <ImageView
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml
index a24b1cf..1842c56 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_toolbar_menu_item.xml
@@ -16,6 +16,7 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     style="@style/Widget.CarUi.Toolbar.MenuItem.IndividualContainer">
@@ -36,7 +37,8 @@
             android:layout_height="@dimen/car_ui_toolbar_menu_item_icon_size"
             android:layout_gravity="center"
             android:tint="@color/car_ui_toolbar_menu_item_icon_color"
-            android:tintMode="src_in"/>
+            android:tintMode="src_in"
+            tools:ignore="UseAppTint" />
     </FrameLayout>
     <com.android.car.ui.uxr.DrawableStateSwitch
         android:id="@+id/car_ui_toolbar_menu_item_switch"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/raw/car_ui_keep.xml b/car-ui-lib/car-ui-lib/src/main/res/raw/car_ui_keep.xml
index fca5f28..d3864e9 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/raw/car_ui_keep.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/raw/car_ui_keep.xml
@@ -15,59 +15,99 @@
 -->
 <resources xmlns:tools="http://schemas.android.com/tools"
     tools:keep="
-               @attr/CarUi*,
-               @attr/carUi*,
-               @attr/state_ux_restricted,
-               @attr/layout_optimizationLevel,
-               @attr/constraintSet,
+               @attr/barrierAllowsGoneWidgets,
                @attr/barrierDirection,
-               @attr/constraint_referenced_ids,
+               @attr/barrierMargin,
+               @attr/carUi*,
                @attr/chainUseRtl,
-               @attr/title,
+               @attr/circularflow_angles,
+               @attr/circularflow_defaultAngle,
+               @attr/circularflow_defaultRadius,
+               @attr/circularflow_radiusInDP,
+               @attr/circularflow_viewCenter,
+               @attr/constraintSet,
+               @attr/constraint_referenced_ids,
+               @attr/constraint_referenced_tags,
+               @attr/constraint_referenced_tags,
+               @attr/flow_firstHorizontalBias,
+               @attr/flow_firstHorizontalStyle,
+               @attr/flow_firstVerticalBias,
+               @attr/flow_firstVerticalStyle,
+               @attr/flow_horizontalAlign,
+               @attr/flow_horizontalBias,
+               @attr/flow_horizontalGap,
+               @attr/flow_horizontalStyle,
+               @attr/flow_lastHorizontalBias,
+               @attr/flow_lastHorizontalStyle,
+               @attr/flow_lastVerticalBias,
+               @attr/flow_lastVerticalStyle,
+               @attr/flow_maxElementsWrap,
+               @attr/flow_verticalAlign,
+               @attr/flow_verticalBias,
+               @attr/flow_verticalGap,
+               @attr/flow_verticalStyle,
+               @attr/flow_wrapMode,
+               @attr/layoutDescription,
+               @attr/layout_constrainedHeight,
+               @attr/layout_constrainedWidth,
+               @attr/layout_constraintBaseline_creator,
+               @attr/layout_constraintBaseline_toBaselineOf,
+               @attr/layout_constraintBaseline_toBottomOf,
+               @attr/layout_constraintBaseline_toTopOf,
+               @attr/layout_constraintBottom_creator,
+               @attr/layout_constraintBottom_toBottomOf,
+               @attr/layout_constraintBottom_toTopOf,
+               @attr/layout_constraintCircle,
+               @attr/layout_constraintCircleAngle,
+               @attr/layout_constraintCircleRadius,
+               @attr/layout_constraintDimensionRatio,
+               @attr/layout_constraintEnd_toEndOf,
+               @attr/layout_constraintEnd_toStartOf,
                @attr/layout_constraintGuide_begin,
                @attr/layout_constraintGuide_end,
                @attr/layout_constraintGuide_percent,
+               @attr/layout_constraintHeight,
+               @attr/layout_constraintHeight_default,
+               @attr/layout_constraintHeight_max,
+               @attr/layout_constraintHeight_min,
+               @attr/layout_constraintHeight_percent,
+               @attr/layout_constraintHorizontal_bias,
+               @attr/layout_constraintHorizontal_chainStyle,
+               @attr/layout_constraintHorizontal_weight,
+               @attr/layout_constraintLeft_creator,
                @attr/layout_constraintLeft_toLeftOf,
                @attr/layout_constraintLeft_toRightOf,
+               @attr/layout_constraintRight_creator,
                @attr/layout_constraintRight_toLeftOf,
                @attr/layout_constraintRight_toRightOf,
-               @attr/layout_constraintTop_toTopOf,
-               @attr/layout_constraintTop_toBottomOf,
-               @attr/layout_constraintBottom_toTopOf,
-               @attr/layout_constraintBottom_toBottomOf,
-               @attr/layout_constraintBaseline_toBaselineOf,
                @attr/layout_constraintStart_toEndOf,
                @attr/layout_constraintStart_toStartOf,
-               @attr/layout_constraintEnd_toStartOf,
-               @attr/layout_constraintEnd_toEndOf,
-               @attr/layout_goneMarginLeft,
-               @attr/layout_goneMarginTop,
-               @attr/layout_goneMarginRight,
-               @attr/layout_goneMarginBottom,
-               @attr/layout_goneMarginStart,
-               @attr/layout_goneMarginEnd,
-               @attr/layout_constraintHorizontal_bias,
-               @attr/layout_constraintVertical_bias,
-               @attr/layout_constraintWidth_default,
-               @attr/layout_constraintHeight_default,
-               @attr/layout_constraintWidth_min,
-               @attr/layout_constraintWidth_max,
-               @attr/layout_constraintWidth_percent,
-               @attr/layout_constraintHeight_min,
-               @attr/layout_constraintHeight_max,
-               @attr/layout_constraintHeight_percent,
-               @attr/layout_constraintLeft_creator,
+               @attr/layout_constraintTag,
                @attr/layout_constraintTop_creator,
-               @attr/layout_constraintRight_creator,
-               @attr/layout_constraintBottom_creator,
-               @attr/layout_constraintBaseline_creator,
-               @attr/layout_constraintDimensionRatio,
-               @attr/layout_constraintHorizontal_weight,
-               @attr/layout_constraintVertical_weight,
-               @attr/layout_constraintHorizontal_chainStyle,
+               @attr/layout_constraintTop_toBottomOf,
+               @attr/layout_constraintTop_toTopOf,
+               @attr/layout_constraintVertical_bias,
                @attr/layout_constraintVertical_chainStyle,
+               @attr/layout_constraintVertical_weight,
+               @attr/layout_constraintWidth,
+               @attr/layout_constraintWidth_default,
+               @attr/layout_constraintWidth_max,
+               @attr/layout_constraintWidth_min,
+               @attr/layout_constraintWidth_percent,
                @attr/layout_editor_absoluteX,
                @attr/layout_editor_absoluteY,
+               @attr/layout_goneMarginBaseline,
+               @attr/layout_goneMarginBottom,
+               @attr/layout_goneMarginEnd,
+               @attr/layout_goneMarginLeft,
+               @attr/layout_goneMarginRight,
+               @attr/layout_goneMarginStart,
+               @attr/layout_goneMarginTop,
+               @attr/layout_marginBaseline,
+               @attr/layout_optimizationLevel,
+               @attr/layout_wrapBehaviorInParent,
+               @attr/state_ux_restricted,
+               @attr/title,
                @bool/car_ui_*,
                @color/car_ui_*,
                @dimen/car_ui_*,
@@ -112,5 +152,6 @@
                @raw/car_ui_*,
                @string/car_ui_*,
                @style/*CarUi*,
-                "
+               @attr/CarUi*,
+               "
   />
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
index adb1d56..7d85a58 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/attrs.xml
@@ -20,6 +20,8 @@
         <attr name="carUiBaseLayout" format="boolean"/>
         <!-- When set to true, a CarUi Toolbar will be provided in the window decor -->
         <attr name="carUiToolbar" format="boolean"/>
+        <!-- Indicates that this is a CarUi activity. Used to disable CarUi on phone/tv/wear activities -->
+        <attr name="carUiActivity" format="boolean"/>
     </declare-styleable>
 
     <declare-styleable name="CarUiToolbar">
@@ -31,20 +33,8 @@
         <attr name="searchHint" format="string"/>
         <!-- Whether or not to show the MenuItems while searching. Default false. -->
         <attr name="showMenuItemsWhileSearching" format="boolean"/>
-        <!-- Initial state of the toolbar. See the Toolbar.State enum for more information -->
-        <attr name="car_ui_state" format="enum">
-            <enum name="home" value="0"/>
-            <enum name="subpage" value="1"/>
-            <enum name="search" value="2"/>
-        </attr>
         <!-- Whether or not the toolbar should have a background. Default true. -->
         <attr name="showBackground" format="boolean"/>
-        <!-- Mode of the navigation button See the Toolbar.NavButtonMode enum for more information -->
-        <attr name="car_ui_navButtonMode" format="enum">
-            <enum name="back" value="0"/>
-            <enum name="close" value="1"/>
-            <enum name="down" value="2"/>
-        </attr>
         <!-- XML resource of MenuItems. See Toolbar.setMenuItems(int) for more information. -->
         <attr name="menuItems" format="reference"/>
         <!-- Whether or not to show tabs in the SUBPAGE state. Default false -->
@@ -63,7 +53,7 @@
         <!-- Title -->
         <attr name="title"/>
         <!-- Icon -->
-        <attr name="icon" format="reference"/>
+        <attr name="carUiIcon" format="reference"/>
         <!-- True to tint the icon to a consistent color. Default true, all the other booleans default to false -->
         <attr name="tinted" format="boolean"/>
         <!-- Show both the icon and title at the same time -->
@@ -138,13 +128,17 @@
         <attr name="android:orientation" />
         <!-- car ui recyclerview layout reversed -->
         <attr name="reverseLayout" format="boolean" />
+        <!-- car ui recyclerview layoutmanager -->
+        <attr name="layoutManager" format="string" />
     </declare-styleable>
 
     <declare-styleable name="CarUiPreference">
         <!-- Toggle for showing chevron -->
-        <attr name="showChevron" format="boolean" />
+        <attr name="carUiShowChevron" format="boolean" />
         <!-- Display this preference as ux restricted. -->
         <attr name="car_ui_ux_restricted" format="boolean" />
+        <!-- Allow the preference to be clicked when disabled. -->
+        <attr name="carUiClickableWhileDisabled" format="boolean" />
     </declare-styleable>
 
     <declare-styleable name="CarUiTwoActionPreference">
@@ -156,7 +150,7 @@
 
     <declare-styleable name="CarUiTwoActionBasePreference">
         <!-- All of these are disallowed -->
-        <attr name="layout" format="reference"/>
+        <attr name="carUiLayout" format="reference"/>
         <attr name="android:layout" format="reference"/>
         <attr name="widgetLayout" format="reference"/>
         <attr name="android:widgetLayout" format="reference"/>
@@ -180,9 +174,6 @@
     <!-- Theme attribute to specify a default style for all CarUiPreferences -->
     <attr name="carUiPreferenceStyle" format="reference" />
 
-    <!-- Theme attribute to specify a default style for all CarUiRecyclerViews -->
-    <attr name="carUiRecyclerViewStyle" format="reference" />
-
     <attr name="state_ux_restricted" format="boolean" />
 
 </resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
index f0c536d..0c2b717 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/dimens.xml
@@ -170,10 +170,10 @@
     <dimen name="car_ui_dialog_edittext_height">50dp</dimen>
     <dimen name="car_ui_dialog_edittext_margin_top">10dp</dimen>
     <dimen name="car_ui_dialog_edittext_margin_bottom">10dp</dimen>
-    <dimen name="car_ui_dialog_edittext_margin_start">22dp</dimen>
-    <dimen name="car_ui_dialog_edittext_margin_end">22dp</dimen>
+    <dimen name="car_ui_dialog_edittext_margin_start">@dimen/car_ui_padding_5</dimen>
+    <dimen name="car_ui_dialog_edittext_margin_end">@dimen/car_ui_padding_5</dimen>
     <dimen name="car_ui_dialog_icon_size">56dp</dimen>
-    <dimen name="car_ui_dialog_title_margin">24dp</dimen>
+    <dimen name="car_ui_dialog_title_margin">@dimen/car_ui_padding_5</dimen>
 
     <!-- List item  -->
 
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
index defba5f..d455a4b 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/strings.xml
@@ -78,8 +78,8 @@
         ro.build.automotive.ime.wide_screen.enabled
     </string>
 
-    <!-- Name of system property used that contains the package name of the CarUi shared library. -->
-    <string name="car_ui_shared_library_package_system_property_name" translatable="false">
-        ro.build.automotive.car.ui.shared.library.package.name
+    <!-- Authority of content provider in the CarUi plugin. -->
+    <string name="car_ui_plugin_package_provider_authority_name" translatable="false">
+        com.android.car.ui.plugin
     </string>
 </resources>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/styles.xml b/car-ui-lib/car-ui-lib/src/main/res/values/styles.xml
index b8a0e51..246738f 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/styles.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/styles.xml
@@ -43,7 +43,6 @@
 
     <style name="Widget.CarUi.Toolbar.NavIcon">
         <item name="android:tint">@color/car_ui_toolbar_nav_icon_color</item>
-        <item name="android:src">@drawable/car_ui_icon_arrow_back</item>
         <item name="android:background">@drawable/car_ui_toolbar_menu_item_icon_ripple</item>
     </style>
 
@@ -133,10 +132,6 @@
         <item name="android:textAppearance">@style/TextAppearance.CarUi.Widget.Toolbar.Tab</item>
     </style>
 
-    <style name="Widget.CarUi.CarUiRecyclerView">
-        <item name="android:scrollbars">vertical</item>
-    </style>
-
     <style name="Widget.CarUi.AlertDialog"/>
 
     <style name="Widget.CarUi.AlertDialog.HeaderContainer">
@@ -226,7 +221,6 @@
 
     <style name="PreferenceFragment.CarUi">
         <item name="android:divider">?android:attr/listDivider</item>
-        <!-- TODO(b/150230923) change this to car_ui_preference_fragment -->
         <item name="android:layout">@layout/car_ui_preference_fragment</item>
     </style>
 
@@ -282,8 +276,8 @@
 
     <style name="TextAppearance.CarUi.PreferenceEditTextDialogMessage" parent="TextAppearance.CarUi.Body3"/>
 
-    <style name="TextAppearance.CarUi.AlertDialog.Title" parent="TextAppearance.CarUi.Body3"/>
-    <style name="TextAppearance.CarUi.AlertDialog.Subtitle" parent="TextAppearance.CarUi.Sub3"/>
+    <style name="TextAppearance.CarUi.AlertDialog.Title" parent="TextAppearance.CarUi.Body1"/>
+    <style name="TextAppearance.CarUi.AlertDialog.Subtitle" parent="TextAppearance.CarUi.Body3"/>
 
     <style name="TextAppearance.CarUi.Widget" parent="android:TextAppearance.DeviceDefault.Widget"/>
 
diff --git a/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
index fbca1d7..597a49a 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
+++ b/car-ui-lib/car-ui-lib/src/main/res/values/themes.xml
@@ -21,6 +21,7 @@
         <!-- TODO(b/150230923) change to true when other apps are ready -->
         <item name="carUiBaseLayout">false</item>
         <item name="carUiToolbar">false</item>
+        <item name="carUiActivity">true</item>
 
         <!-- Attributes from: Base.V7.Theme.AppCompat -->
 
@@ -206,9 +207,6 @@
 
         <item name="preferenceTheme">@style/CarUiPreferenceTheme</item>
 
-        <!-- Used by CarUiRecyclerView -->
-        <item name="carUiRecyclerViewStyle">@style/Widget.CarUi.CarUiRecyclerView</item>
-
         <!-- textAppearance -->
         <item name="android:textAppearance">@style/TextAppearance.CarUi</item>
         <!--@color/transparent does not completely remove highlight  -->
diff --git a/car-ui-lib/documentation/images/shared_library_setup.png b/car-ui-lib/documentation/images/plugin_setup.png
similarity index 100%
rename from car-ui-lib/documentation/images/shared_library_setup.png
rename to car-ui-lib/documentation/images/plugin_setup.png
Binary files differ
diff --git a/car-ui-lib/gradle/wrapper/gradle-wrapper.jar b/car-ui-lib/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/car-ui-lib/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/car-ui-lib/gradle/wrapper/gradle-wrapper.properties b/car-ui-lib/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0f80bbf
--- /dev/null
+++ b/car-ui-lib/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/car-ui-lib/gradlew b/car-ui-lib/gradlew
index fbd7c51..4f906e0 100755
--- a/car-ui-lib/gradlew
+++ b/car-ui-lib/gradlew
@@ -130,7 +130,7 @@
 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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
diff --git a/car-ui-lib/gradlew.bat b/car-ui-lib/gradlew.bat
index a9f778a..ac1b06f 100644
--- a/car-ui-lib/gradlew.bat
+++ b/car-ui-lib/gradlew.bat
@@ -40,7 +40,7 @@
 

 set JAVA_EXE=java.exe

 %JAVA_EXE% -version >NUL 2>&1

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

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

 

 echo.

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

@@ -54,7 +54,7 @@
 set JAVA_HOME=%JAVA_HOME:"=%

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

 

-if exist "%JAVA_EXE%" goto init

+if exist "%JAVA_EXE%" goto execute

 

 echo.

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

@@ -64,21 +64,6 @@
 

 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

 

@@ -86,7 +71,7 @@
 

 

 @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%

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

 

 :end

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

diff --git a/car-ui-lib/oem-apis/README.md b/car-ui-lib/oem-apis/README.md
deleted file mode 100644
index 40d4b42..0000000
--- a/car-ui-lib/oem-apis/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Car-ui-lib OEM APIs
-
-```
-#############################################
-#                  WARNING                  #
-#############################################
-# The OEM APIs as they appear on this       #
-# branch of android are not finalized!      #
-# If a shared library is built using them,  #
-# it will cause apps to crash!              #
-#                                           #
-# Please get the OEM APIs from a later      #
-# branch of android instead.                #
-#############################################
-```
-
-These APIs allow OEMs to build a shared library for
-car-ui-lib that can supply custom implementations
-of car-ui-lib components. See
-SharedLibraryFactorySingleton for information
-on the entrypoint to the shared library.
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusAreaOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusAreaOEMV1.java
similarity index 89%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusAreaOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusAreaOEMV1.java
index 017ec41..3fce4d1 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusAreaOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusAreaOEMV1.java
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis;
+package com.android.car.ui.plugin.oemapis;
 
 import android.view.View;
 import android.widget.LinearLayout;
 
 /**
  * The OEM interface for a FocusArea. Unlike most components, the FocusArea has it's implementation
- * in the static library, and this interface is to give the shared library access to it. The
- * shared library is not expected to implement this interface.
+ * in the static library, and this interface is to give the plugin access to it. The
+ * plugin is not expected to implement this interface.
  * <p>
- * See {@link SharedLibraryFactoryOEMV1#setRotaryFactories}
+ * See {@link PluginFactoryOEMV1#setRotaryFactories}
  */
 public interface FocusAreaOEMV1 {
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusParkingViewOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusParkingViewOEMV1.java
similarity index 88%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusParkingViewOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusParkingViewOEMV1.java
index f6ec374..5074dcc 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/FocusParkingViewOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/FocusParkingViewOEMV1.java
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis;
+package com.android.car.ui.plugin.oemapis;
 
 import android.view.View;
 
 /**
  * The OEM interface for a FocusParkingView. Unlike most components, the FocusParkingView has it's
- * implementation in the static library, and this interface is to give the shared library access to
- * it. The shared library is not expected to implement this interface.
+ * implementation in the static library, and this interface is to give the plugin access to
+ * it. The plugin is not expected to implement this interface.
  * <p>
- * See {@link SharedLibraryFactoryOEMV1#setRotaryFactories}
+ * See {@link PluginFactoryOEMV1#setRotaryFactories}
  */
 public interface FocusParkingViewOEMV1 {
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/InsetsOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/InsetsOEMV1.java
similarity index 97%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/InsetsOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/InsetsOEMV1.java
index 0e501d6..7ac1a4d 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/InsetsOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/InsetsOEMV1.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis;
+package com.android.car.ui.plugin.oemapis;
 
 import java.util.Objects;
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryFactoryOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV1.java
similarity index 62%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryFactoryOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV1.java
index db45204..e2ccc03 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryFactoryOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV1.java
@@ -13,20 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis;
+package com.android.car.ui.plugin.oemapis;
 
 import android.content.Context;
 import android.view.View;
 
-import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
 
-import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -34,18 +28,17 @@
  * This interface contains methods to create customizable carui components.
  * <p>
  * It returns them as their OEM-versioned interfaces (i.e. ToolbarControllerOEMV1) and is versioned
- * itself so that no additional reflection or casting is necessary once the SharedLibraryFactory has
+ * itself so that no additional reflection or casting is necessary once the PluginFactory has
  * been created.
  * <p>
- * Multiple of these can be provided via {@link SharedLibraryVersionProviderOEMV1} to allow shared
- * libraries to provide an old implementation for old apps, and a newer implementation for newer
- * apps.
+ * Multiple of these can be provided via {@link PluginVersionProviderOEMV1} to allow plugins
+ * to provide an old implementation for old apps, and a newer implementation for newer apps.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public interface SharedLibraryFactoryOEMV1 {
+public interface PluginFactoryOEMV1 {
 
     /**
-     * Gives the shared library access to two factories that will create FocusParkingViews and
+     * Gives the plugin access to two factories that will create FocusParkingViews and
      * FocusAreas. These views have their implementation in the static car-ui-lib.
      * <p>
      * When {@link #installBaseLayoutAround} creates a base layout, it should include a
@@ -64,6 +57,13 @@
     /**
      * Creates the base layout, and optionally the toolbar.
      *
+     * @param sourceContext The context that will end up using this component. This
+     *                      context must not be used for inflating views, use the plugin context for
+     *                      that. This is used for two purposes: to add the correct configuration to
+     *                      the plugin context via {@code pluginContext.createConfigurationContext(
+     *                      sourceContext.getResources().getConfiguration()} before inflating views,
+     *                      and to pass to the rotary factories provided via
+     *                      {@link #setRotaryFactories}.
      * @param contentView           The view to install the base layout around.
      * @param insetsChangedListener A method to call when the insets change.
      * @param toolbarEnabled        Whether or not to add a toolbar to the base layout.
@@ -73,6 +73,7 @@
      * @return A {@link ToolbarControllerOEMV1} or null if {@code toolbarEnabled} was false.
      */
     ToolbarControllerOEMV1 installBaseLayoutAround(
+            Context sourceContext,
             View contentView,
             Consumer<InsetsOEMV1> insetsChangedListener,
             boolean toolbarEnabled,
@@ -89,20 +90,14 @@
     /**
      * Creates a app styled view.
      *
+     * @param sourceContext The context that will end up using this component. This context must not
+     *                      be used for inflating views, use the plugin context for that. This
+     *                      is used for two purposes: to add the correct configuration to the plugin
+     *                      context via {@code pluginContext.createConfigurationContext(
+     *                      sourceContext.getResources().getConfiguration()} before inflating views,
+     *                      and to pass to the rotary factories provided via
+     *                      {@link #setRotaryFactories}.
      * @return the view used for app styled view.
      */
-    AppStyledViewControllerOEMV1 createAppStyledView();
-
-    /**
-     * Creates an instance of CarUiRecyclerView
-     *
-     * @param context The visual context to create views with.
-     * @param attrs   An object containing initial attributes for the button.
-     */
-    RecyclerViewOEMV1 createRecyclerView(Context context, RecyclerViewAttributesOEMV1 attrs);
-
-    /**
-     * Creates an instance of list item adapter
-     */
-    AdapterOEMV1<? extends ViewHolderOEMV1> createListItemAdapter(List<ListItemOEMV1> items);
+    AppStyledViewControllerOEMV1 createAppStyledView(Context sourceContext);
 }
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV2.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV2.java
new file mode 100644
index 0000000..6dc465d
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginFactoryOEMV2.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 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.ui.plugin.oemapis;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * This plugin factory is not finalized and thus not ready for production use.
+ * Please use {@link PluginFactoryOEMV1} instead.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public interface PluginFactoryOEMV2 {
+    /**
+     * Gives the plugin access to two factories that will create FocusParkingViews and
+     * FocusAreas. These views have their implementation in the static car-ui-lib.
+     * <p>
+     * When {@link #installBaseLayoutAround} creates a base layout, it should include a
+     * FocusParkingView for rotary to work properly. If the base layout has a toolbar, it should
+     * also be wrapped in a FocusArea.
+     *
+     * @param focusParkingViewFactory a function that will infinitely return new instances of
+     *                                FocusParkingView
+     * @param focusAreaFactory        a function that will infinitely return new instances of
+     *                                FocusArea
+     */
+    void setRotaryFactories(
+            Function<Context, FocusParkingViewOEMV1> focusParkingViewFactory,
+            Function<Context, FocusAreaOEMV1> focusAreaFactory);
+
+    /**
+     * Creates the base layout, and optionally the toolbar.
+     *
+     * @param sourceContext The context that will end up using this component. This context must not
+     *                      be used for inflating views, use the plugin context for that. This
+     *                      is used for two purposes: to add the correct configuration to the plugin
+     *                      context via {@code pluginContext.createConfigurationContext(
+     *                      sourceContext.getResources().getConfiguration()} before inflating views,
+     *                      and to pass to the rotary factories provided via
+     *                      {@link #setRotaryFactories}.
+     * @param contentView           The view to install the base layout around.
+     * @param insetsChangedListener A method to call when the insets change.
+     * @param toolbarEnabled        Whether or not to add a toolbar to the base layout.
+     * @param fullscreen            Whether or not this base layout / toolbar is taking up the whole
+     *                              screen. This can be used to decide whether or not to add
+     *                              decorations around the edge of it.
+     * @return A {@link ToolbarControllerOEMV1} or null if {@code toolbarEnabled} was false.
+     */
+    ToolbarControllerOEMV1 installBaseLayoutAround(
+            Context sourceContext,
+            View contentView,
+            Consumer<InsetsOEMV1> insetsChangedListener,
+            boolean toolbarEnabled,
+            boolean fullscreen);
+
+    /**
+     * If implementation of the library would like to opt out of controlling the base layout and
+     * subsequently the toolbar they can do so by returning false from this method.
+     *
+     * @return false if {@link #installBaseLayoutAround} should not be called for this library
+     */
+    boolean customizesBaseLayout();
+
+    /**
+     * Creates a app styled view.
+     *
+     * @param sourceContext The context that will end up using this component. This context must not
+     *                      be used for inflating views, use the plugin context for that. This
+     *                      is used for two purposes: to add the correct configuration to the plugin
+     *                      context via {@code pluginContext.createConfigurationContext(
+     *                      sourceContext.getResources().getConfiguration()} before inflating views,
+     *                      and to pass to the rotary factories provided via
+     *                      {@link #setRotaryFactories}.
+     * @return the view used for app styled view.
+     */
+    AppStyledViewControllerOEMV1 createAppStyledView(Context sourceContext);
+
+    /**
+     * Creates an instance of CarUiRecyclerView
+     *
+     * @param sourceContext The context that will end up using this component. This context must not
+     *                      be used for inflating views, use the plugin context for that. This
+     *                      is used for two purposes: to add the correct configuration to the plugin
+     *                      context via {@code pluginContext.createConfigurationContext(
+     *                      sourceContext.getResources().getConfiguration()} before inflating views,
+     *                      and to pass to the rotary factories provided via
+     *                      {@link #setRotaryFactories}.
+     * @param attrs   An object containing initial attributes for the button.
+     */
+    RecyclerViewOEMV1 createRecyclerView(
+            Context sourceContext,
+            RecyclerViewAttributesOEMV1 attrs);
+
+    /**
+     * Creates an instance of list item adapter
+     */
+    AdapterOEMV1<? extends ViewHolderOEMV1> createListItemAdapter(List<ListItemOEMV1> items);
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginVersionProviderOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginVersionProviderOEMV1.java
new file mode 100644
index 0000000..3503e6d
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/PluginVersionProviderOEMV1.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis;
+
+import android.content.Context;
+
+/**
+ * An interface for objects that support providing a list a supported versions of
+ * {@link PluginFactoryOEMV1} to the app. See {@link #getPluginFactory}
+ * for more information.
+ */
+public interface PluginVersionProviderOEMV1 {
+    /**
+     * Returns an object that implements {@link PluginFactoryOEMV1} or a later version.
+     *
+     * OEMs should aim to return the highest version of the factory possible that is <=
+     * {@code maxVersion}. If the plugin is not able to provide that version,
+     * it may return null, in which case car-ui-lib will fall back to it's static,
+     * uncustomized implementation.
+     *
+     * The plugin may also choose to return different PluginFactories based on
+     * certain conditions, like what type of device this is, or what app it's being used in.
+     *
+     * @param maxVersion The maximum version of {@link PluginFactoryOEMV1} supported by the
+     *                   app.
+     * @param context The plugin's context. It uses the plugin's classloader,
+     *                so layout inflaters created from it can use views defined in the plugin.
+     * @param packageName The package name of the app creating the plugin. Can be used
+     *                    to provide per-app customizations.
+     *
+     * @return An object implementing {@link PluginFactoryOEMV1} for a version <=
+     *         {@code maxVersion}.
+     */
+    Object getPluginFactory(int maxVersion, Context context, String packageName);
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/TextOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/TextOEMV1.java
new file mode 100644
index 0000000..c996259
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/TextOEMV1.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2021 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.ui.plugin.oemapis;
+
+import android.text.SpannableStringBuilder;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The OEM interface for text that provides variants that should be rendered in cases where
+ * preferred text cannot.
+ */
+public class TextOEMV1 {
+    private final int mMaxLines;
+    private final int mMaxChars;
+    private final List<CharSequence> mVariants;
+
+    /**
+     * Convenience method that returns a single {@link CharSequence} that is a combination of the
+     * preferred text of a list of {@link TextOEMV1}, separated by line breaks.
+     */
+    public static CharSequence combineMultiLine(List<TextOEMV1> lines) {
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        CharSequence delimiter = "";
+        for (TextOEMV1 line : lines) {
+            builder.append(delimiter)
+                    .append(line.getPreferredText() == null ? " " : line.getPreferredText());
+            delimiter = "\n";
+        }
+        return builder;
+    }
+
+    private TextOEMV1(Builder builder) {
+        mVariants = builder.mVariants;
+        mMaxChars = builder.mMaxChars;
+        mMaxLines = builder.mMaxLines;
+    }
+
+    /**
+     * Returns the maximum number of lines the text should be displayed on when width constraints
+     * force the text to be wrapped
+     */
+    public int getMaxLines() {
+        return mMaxLines;
+    }
+
+    /**
+     * Returns the maximum number of characters that should be displayed for the text
+     */
+    public int getMaxChars() {
+        return mMaxChars;
+    }
+
+    /**
+     * Returns the list of text variants for this {@link TextOEMV1}.
+     */
+    public List<CharSequence> getTextVariants() {
+        return mVariants;
+    }
+
+    /**
+     * Returns the preferred text to render for this {@link TextOEMV1}.
+     */
+    public CharSequence getPreferredText() {
+        return mVariants.get(0);
+    }
+
+    /**
+     * A builder of {@link TextOEMV1}.
+     */
+    public static final class Builder {
+        private int mMaxLines = Integer.MAX_VALUE;
+        private int mMaxChars = Integer.MAX_VALUE;
+        private final List<CharSequence> mVariants;
+
+        /**
+         * Returns a new instance of a {@link Builder}.
+         *
+         * @param text text to display
+         */
+        public Builder(CharSequence text) {
+            this(Collections.singletonList(text));
+        }
+
+        /**
+         * Returns a new instance of a {@link Builder}.
+         *
+         * @param variants list of text variants. Variants provide alternative text to be used to
+         *                 avoid truncation. Provide variants in order of preference.
+         */
+        public Builder(List<CharSequence> variants) {
+            mVariants = variants;
+        }
+
+        /**
+         * Sets the maximum number of characters that should be displayed.
+         */
+        public Builder setMaxChars(int chars) {
+            mMaxChars = chars;
+            return this;
+        }
+
+        /**
+         * Sets the maximum number of lines the text should be displayed on when width constraints
+         * force the text to be wrapped. Text that exceeds the maximum number of lines is
+         * ellipsized.
+         */
+        public Builder setMaxLines(int lines) {
+            mMaxLines = lines;
+            return this;
+        }
+
+        /**
+         * Returns a {@link TextOEMV1} for this {@link Builder}.
+         */
+        public TextOEMV1 build() {
+            return new TextOEMV1(this);
+        }
+    }
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/appstyledview/AppStyledViewControllerOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/appstyledview/AppStyledViewControllerOEMV1.java
similarity index 66%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/appstyledview/AppStyledViewControllerOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/appstyledview/AppStyledViewControllerOEMV1.java
index 0aed7e0..382a976 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/appstyledview/AppStyledViewControllerOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/appstyledview/AppStyledViewControllerOEMV1.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.appstyledview;
+package com.android.car.ui.plugin.oemapis.appstyledview;
 
 import android.view.View;
 import android.view.WindowManager;
@@ -23,20 +23,29 @@
 public interface AppStyledViewControllerOEMV1 {
 
     /**
-     * Creates a app styled view.
+     * Gets the view to display. This view will contain the content view set in {@link #setContent}.
      *
-     * @param content app content view.
      * @return the view used for app styled view.
      */
-    View getAppStyledView(View content);
+    View getView();
+
+    /**
+     * Sets the content view to be contained within this AppStyledView.
+     */
+    void setContent(View content);
 
     /**
      * Sets a {@link Runnable} to be called whenever the close icon is clicked.
      */
-    void setOnCloseClickListener(Runnable listener);
+    void setOnBackClickListener(Runnable listener);
+
+    int NAV_ICON_DISABLED = 0;
+    int NAV_ICON_BACK = 1;
+    int NAV_ICON_CLOSE = 2;
 
     /**
-     * Sets the nav icon to be used.
+     * Sets the nav icon to be used. Can be set to one of {@link #NAV_ICON_DISABLED},
+     * {@link #NAV_ICON_BACK} or {@link #NAV_ICON_CLOSE}.
      */
     void setNavIcon(int navIcon);
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterDataObserverOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterDataObserverOEMV1.java
similarity index 96%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterDataObserverOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterDataObserverOEMV1.java
index 628bb74..87355bc 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterDataObserverOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterDataObserverOEMV1.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 /**
  * {@link androidx.recyclerview.widget.RecyclerView.Adapter#registerAdapterDataObserver}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterOEMV1.java
new file mode 100644
index 0000000..5b89b82
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/AdapterOEMV1.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis.recyclerview;
+
+import android.view.ViewGroup;
+
+/**
+ * See {@link androidx.recyclerview.widget.RecyclerView.Adapter}
+ *
+ * @param <V> A class that extends ViewHolder that will be used by the adapter.
+ */
+public interface AdapterOEMV1<V extends ViewHolderOEMV1> {
+
+    int ALLOW = 0;
+    int PREVENT_WHEN_EMPTY = 1;
+    int PREVENT = 2;
+
+    /**
+     * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+     */
+    int UNLIMITED = -1;
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemCount()}
+     */
+    int getItemCount();
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemId(int)}
+     */
+    long getItemId(int position);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemViewType(int)}
+     */
+    int getItemViewType(int position);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getStateRestorationPolicy()}
+     */
+    int getStateRestorationPolicyInt();
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onAttachedToRecyclerView}
+     */
+    void onAttachedToRecyclerView(RecyclerViewOEMV1 recyclerView);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#bindViewHolder}
+     */
+    void bindViewHolder(V holder, int position);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#createViewHolder}
+     */
+    V createViewHolder(ViewGroup parent, int viewType);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onDetachedFromRecyclerView}
+     */
+    void onDetachedFromRecyclerView(RecyclerViewOEMV1 recyclerView);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onFailedToRecycleView}
+     */
+    boolean onFailedToRecycleView(V holder);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewAttachedToWindow}
+     */
+    void onViewAttachedToWindow(V holder);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewDetachedFromWindow}
+     */
+    void onViewDetachedFromWindow(V holder);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewRecycled}
+     */
+    void onViewRecycled(V holder);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#registerAdapterDataObserver}
+     */
+    void registerAdapterDataObserver(AdapterDataObserverOEMV1 observer);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#unregisterAdapterDataObserver}
+     */
+    void unregisterAdapterDataObserver(AdapterDataObserverOEMV1 observer);
+
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.Adapter#hasStableIds}
+     */
+    boolean hasStableIds();
+
+    /**
+     * 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);
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ContentListItemOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ContentListItemOEMV1.java
similarity index 87%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ContentListItemOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ContentListItemOEMV1.java
index e672b4f..46fddaa 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ContentListItemOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ContentListItemOEMV1.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 import android.graphics.drawable.Drawable;
 import android.text.SpannableString;
 
+import com.android.car.ui.plugin.oemapis.TextOEMV1;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -78,8 +80,8 @@
     }
 
     private final Action mAction;
-    private final SpannableString mTitle;
-    private final List<SpannableString> mBody;
+    private final TextOEMV1 mTitle;
+    private final List<TextOEMV1> mBody;
     private final Drawable mIcon;
     private final IconType mPrimaryIconType;
     private final Drawable mSupplementalIcon;
@@ -87,6 +89,7 @@
     private final boolean mIsEnabled;
     private final boolean mIsActivated;
     private final boolean mIsActionDividerVisible;
+    private final boolean mIsSecure;
     private final Consumer<ContentListItemOEMV1> mOnClickListener;
     private final Consumer<ContentListItemOEMV1> mOnCheckedChangeListener;
     private final Consumer<ContentListItemOEMV1> mSupplementalIconOnClickListener;
@@ -102,6 +105,7 @@
         mIsEnabled = builder.mIsEnabled;
         mIsActivated = builder.mIsActivated;
         mIsActionDividerVisible = builder.mIsActionDividerVisible;
+        mIsSecure = builder.mIsSecure;
         mOnClickListener = builder.mOnClickListener;
         mOnCheckedChangeListener = builder.mOnCheckedChangeListener;
         mSupplementalIconOnClickListener = builder.mSupplementalIconOnClickListener;
@@ -110,14 +114,14 @@
     /**
      * Returns the title of the item.
      */
-    public SpannableString getTitle() {
+    public TextOEMV1 getTitle() {
         return mTitle;
     }
 
     /**
      * Returns the body of the item.
      */
-    public List<SpannableString> getBody() {
+    public List<TextOEMV1> getBody() {
         return mBody;
     }
 
@@ -165,6 +169,14 @@
     }
 
     /**
+     * Returns {@code true} if list item is secure. A secure list item must not call it's click
+     * listeners when there is a full or partial overlay on the window.
+     */
+    public boolean isSecure() {
+        return mIsSecure;
+    }
+
+    /**
      * Returns the action type for the item.
      */
     public Action getAction() {
@@ -225,15 +237,16 @@
      */
     public static final class Builder {
         private final Action mAction;
-        private SpannableString mTitle;
-        private List<SpannableString> mBody;
+        private TextOEMV1 mTitle;
+        private List<TextOEMV1> mBody;
         private Drawable mIcon;
         private IconType mPrimaryIconType = IconType.STANDARD;
         private Drawable mSupplementalIcon;
         private boolean mIsChecked = false;
         private boolean mIsEnabled = true;
         private boolean mIsActivated = false;
-        private boolean mIsActionDividerVisible;
+        private boolean mIsActionDividerVisible = false;
+        private boolean mIsSecure = false;
         private Consumer<ContentListItemOEMV1> mOnClickListener;
         private Consumer<ContentListItemOEMV1> mOnCheckedChangeListener;
         private Consumer<ContentListItemOEMV1> mSupplementalIconOnClickListener;
@@ -251,6 +264,16 @@
          * @param text text to display as title
          */
         public Builder setTitle(SpannableString text) {
+            mTitle = new TextOEMV1.Builder(text).build();
+            return this;
+        }
+
+        /**
+         * Sets the title of the item.
+         *
+         * @param text text to display as title
+         */
+        public Builder setTitle(TextOEMV1 text) {
             mTitle = text;
             return this;
         }
@@ -261,7 +284,7 @@
          * @param text text to display as body text.
          */
         public Builder setBody(SpannableString text) {
-            mBody = Collections.singletonList(text);
+            mBody = Collections.singletonList(new TextOEMV1.Builder(text).build());
             return this;
         }
 
@@ -269,9 +292,9 @@
          * Sets the body of the item.
          *
          * @param textList list of text to display as body text. Each {@link SpannableString} in the
-         *                list will be rendered on a new line, separated by a line break.
+         *                 list will be rendered on a new line, separated by a line break.
          */
-        public Builder setBody(List<SpannableString> textList) {
+        public Builder setBody(List<TextOEMV1> textList) {
             mBody = textList;
             return this;
         }
@@ -329,6 +352,17 @@
         }
 
         /**
+         * Sets if the list item is secure or not. If it is secure, it won't sent any click events
+         * if there is a full or partial overlay on the screen when they're clicked.
+         *
+         * @param secure If the list item is secure or not.
+         */
+        public Builder setSecure(boolean secure) {
+            mIsSecure = secure;
+            return this;
+        }
+
+        /**
          * Sets supplemental icon to be displayed in a list item.
          *
          * @param icon the Drawable to set as the icon, or null to clear the content.
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/HeaderListItemOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/HeaderListItemOEMV1.java
similarity index 96%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/HeaderListItemOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/HeaderListItemOEMV1.java
index dc7c6f5..e5526b2 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/HeaderListItemOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/HeaderListItemOEMV1.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 import android.text.SpannableString;
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/LayoutStyleOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/LayoutStyleOEMV1.java
similarity index 80%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/LayoutStyleOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/LayoutStyleOEMV1.java
index 54895e1..7766e0d 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/LayoutStyleOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/LayoutStyleOEMV1.java
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 /**
- * Class for storing recyclerview layout style informatioon.
+ * Class for storing recyclerview layout style information.
  */
 public interface LayoutStyleOEMV1 {
 
@@ -38,6 +38,8 @@
     /** Returns true if layout is reversed */
     boolean getReverseLayout();
 
-    /** Returns a wrapper {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup} */
-    SpanSizeLookupOEMV1 getSpanSizeLookup();
+    /** {@link androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup#getSpanSize} */
+    default int getSpanSize(int position) {
+        return 1;
+    }
 }
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ListItemOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ListItemOEMV1.java
similarity index 91%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ListItemOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ListItemOEMV1.java
index 649d8a5..59d5dbd 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ListItemOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ListItemOEMV1.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 /**
  * The OEM interface for a list item.
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnChildAttachStateChangeListenerOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnChildAttachStateChangeListenerOEMV1.java
new file mode 100644
index 0000000..f719781
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnChildAttachStateChangeListenerOEMV1.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis.recyclerview;
+
+import android.view.View;
+
+/** See {@link androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListenerOEMV1} */
+public interface OnChildAttachStateChangeListenerOEMV1 {
+
+    /**
+     * see {@link RecyclerView.OnChildAttachStateChangeListenerOEMV1#onChildViewAttachedToWindow}
+     */
+    void onChildViewAttachedToWindow(View view);
+
+    /**
+     * see {@link RecyclerView.OnChildAttachStateChangeListenerOEMV1#onChildViewDetachedFromWindow}
+     */
+    void onChildViewDetachedFromWindow(View view);
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/OnScrollListenerOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnScrollListenerOEMV1.java
similarity index 85%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/OnScrollListenerOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnScrollListenerOEMV1.java
index 3ebec25..6a53aa5 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/OnScrollListenerOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/OnScrollListenerOEMV1.java
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 /** See {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener} */
 public interface OnScrollListenerOEMV1 {
 
-    /** See {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener#onScrollStateChanged} */
+    /**
+     * See {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener#onScrollStateChanged}
+     */
     void onScrollStateChanged(RecyclerViewOEMV1 recyclerView, int newState);
 
     /** See {@link androidx.recyclerview.widget.RecyclerView.OnScrollListener#onScrolled} */
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java
new file mode 100644
index 0000000..832cbc6
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis.recyclerview;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Set of attributes passed from UI/layout to the oem implementation
+ */
+public interface RecyclerViewAttributesOEMV1 {
+
+    int SIZE_SMALL = 0;
+    int SIZE_MEDIUM = 1;
+    int SIZE_LARGE = 2;
+
+    /** Returns if rotary scroll is enabled */
+    boolean isRotaryScrollEnabled();
+
+    /** Describes the expected relative size of the
+     * {@link androidx.recyclerview.widget.RecyclerView}. The list may be rendered differently for
+     * each expected size.
+     */
+    int getSize();
+
+    /** Returns information regarding the layout style */
+    LayoutStyleOEMV1 getLayoutStyle();
+
+    /** Returns requested width of the list view */
+    int getLayoutWidth();
+
+    /** Returns requested height of the list view */
+    int getLayoutHeight();
+
+    /** Returns requested minimum width of the list view */
+    int geMinWidth();
+
+    /** Returns requested minimum height of the list view */
+    int getMinHeight();
+
+    /** Returns requested left padding for the list view */
+    int getPaddingLeft();
+
+    /** Returns requested right padding for the list view */
+    int getPaddingRight();
+
+    /** Returns requested top padding for the list view */
+    int getPaddingTop();
+
+    /** Returns requested bottom padding for the list view */
+    int getPaddingBottom();
+
+    /** Returns requested left margin for the list view */
+    int getMarginLeft();
+
+    /** Returns requested right margin for the list view */
+    int getMarginRight();
+
+    /** Returns requested top margin for the list view */
+    int getMarginTop();
+
+    /** Returns requested bottom margin for the list view */
+    int getMarginBottom();
+
+    /** Returns background specified for the list view */
+    Drawable getBackground();
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewOEMV1.java
new file mode 100644
index 0000000..6295790
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/RecyclerViewOEMV1.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis.recyclerview;
+
+import android.view.View;
+
+/**
+ * {@link androidx.recyclerview.widget.RecyclerView}
+ */
+public interface RecyclerViewOEMV1 {
+
+    /**
+     * The RecyclerView is not currently scrolling.
+     *
+     * @see #getScrollState()
+     */
+    int SCROLL_STATE_IDLE = 0;
+
+    /**
+     * The RecyclerView is currently being dragged by outside input such as user touch input.
+     *
+     * @see #getScrollState()
+     */
+    int SCROLL_STATE_DRAGGING = 1;
+
+    /**
+     * The RecyclerView is currently animating to a final position while not under
+     * outside control.
+     *
+     * @see #getScrollState()
+     */
+    int SCROLL_STATE_SETTLING = 2;
+
+    /** {@link RecyclerView#setAdapter(Adapter)} */
+    <V extends ViewHolderOEMV1> void setAdapter(AdapterOEMV1<V> adapter);
+
+    /** {@link RecyclerView#addOnScrollListener} */
+    void addOnScrollListener(OnScrollListenerOEMV1 listener);
+
+    /** {@link RecyclerView#removeOnScrollListener} */
+    void removeOnScrollListener(OnScrollListenerOEMV1 listener);
+
+    /** {@link RecyclerView#clearOnScrollListeners()} */
+    void clearOnScrollListeners();
+
+    /** {@link RecyclerView#scrollToPosition(int)} */
+    void scrollToPosition(int position);
+
+    /** {@link RecyclerView#smoothScrollBy(int, int)} */
+    void smoothScrollBy(int dx, int dy);
+
+    /** {@link RecyclerView#smoothScrollToPosition(int)} */
+    void smoothScrollToPosition(int position);
+
+    /** {@link RecyclerView#setHasFixedSize(boolean)} */
+    void setHasFixedSize(boolean hasFixedSize);
+
+    /** {@link RecyclerView#hasFixedSize()} */
+    boolean hasFixedSize();
+
+    /**
+     * set {@link LayoutStyleOEMV1}. This is the replacement for
+     * {@link androidx.recyclerview.widget.RecyclerView.LayoutManager}
+     */
+    void setLayoutStyle(LayoutStyleOEMV1 layoutStyle);
+
+    /**
+     * set {@link LayoutStyleOEMV1}. This is the replacement for
+     * {@link androidx.recyclerview.widget.RecyclerView.LayoutManager}
+     */
+    LayoutStyleOEMV1 getLayoutStyle();
+
+    /**
+     * Returns the view that will be displayed on the screen.
+     */
+    View getView();
+
+    /** {@link android.view.View#setPadding(int, int, int, int)} */
+    void setPadding(int left, int top, int right, int bottom);
+
+    /** {@link android.view.View#setPaddingRelative(int, int, int, int)} */
+    void setPaddingRelative(int start, int top, int end, int bottom);
+
+    /** {@link androidx.recyclerview.widget.RecyclerView#setClipToPadding(boolean)} */
+    void setClipToPadding(boolean clipToPadding);
+
+    /** see {@link LinearLayoutManager#findFirstCompletelyVisibleItemPosition()} */
+    int findFirstCompletelyVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findFirstVisibleItemPosition()} */
+    int findFirstVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findLastCompletelyVisibleItemPosition()} */
+    int findLastCompletelyVisibleItemPosition();
+
+    /** see {@link LinearLayoutManager#findLastVisibleItemPosition()} */
+    int findLastVisibleItemPosition();
+
+    /** see {@link RecyclerView#getScrollState()} */
+    int getScrollState();
+
+    /** see {@link View#setContentDescription(CharSequence)} */
+    void setContentDescription(CharSequence contentDescription);
+
+    /** see {@link View#setAlpha(float)} */
+    void setAlpha(float alpha);
+
+    /** see {@link OrientationHelper#getEndAfterPadding()} */
+    int getEndAfterPadding();
+
+    /** see {@link OrientationHelper#getStartAfterPadding()} */
+    int getStartAfterPadding();
+
+    /** see {@link OrientationHelper#getTotalSpace()} */
+    int getTotalSpace();
+
+    /**
+     * see {@link LayoutManager#getChildCount()}
+     * Prefer this method over {@link View#getChildCount()}
+     */
+    int getRecyclerViewChildCount();
+
+    /**
+     * see {@link LayoutManager#getChildAt(int)}
+     * Prefer this method over {@link View#getChildAt(int)}
+     */
+    View getRecyclerViewChildAt(int index);
+
+    /**
+     * see {@link RecyclerView#findViewHolderForAdapterPosition(int)}
+     */
+    ViewHolderOEMV1 findViewHolderForAdapterPosition(int position);
+
+    /**
+     * see {@link RecyclerView#findViewHolderForLayoutPosition(int)}
+     */
+    ViewHolderOEMV1 findViewHolderForLayoutPosition(int position);
+
+    /** {@link RecyclerView#addOnChildAttachStateChangeListener} */
+    void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListenerOEMV1 listener);
+
+    /** {@link RecyclerView#removeOnChildAttachStateChangeListener} */
+    void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListenerOEMV1 listener);
+
+    /** {@link RecyclerView#clearOnChildAttachStateChangeListener()} */
+    void clearOnChildAttachStateChangeListener();
+
+    /** {@link RecyclerView#getChildLayoutPosition} */
+    int getChildLayoutPosition(View child);
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ViewHolderOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ViewHolderOEMV1.java
similarity index 93%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ViewHolderOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ViewHolderOEMV1.java
index 8226657..1b7ec1c 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/ViewHolderOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/recyclerview/ViewHolderOEMV1.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.recyclerview;
+package com.android.car.ui.plugin.oemapis.recyclerview;
 
 import android.view.View;
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ImeSearchInterfaceOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ImeSearchInterfaceOEMV1.java
similarity index 95%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ImeSearchInterfaceOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ImeSearchInterfaceOEMV1.java
index 44e3ddf..4ac7f60 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ImeSearchInterfaceOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ImeSearchInterfaceOEMV1.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.car.ui.sharedlibrary.oemapis.toolbar;
+package com.android.car.ui.plugin.oemapis.toolbar;
 
 import android.os.Bundle;
 import android.widget.TextView;
@@ -22,7 +22,7 @@
 import java.util.function.Consumer;
 
 /**
- * This is an interface (as in "bridge") between the static lib and the shared lib, so that
+ * This is an interface (as in "bridge") between the static lib and the plugin, so that
  * the static lib can handle showing search results inside the IME.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/MenuItemOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/MenuItemOEMV1.java
new file mode 100644
index 0000000..671b323
--- /dev/null
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/MenuItemOEMV1.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2020 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.ui.plugin.oemapis.toolbar;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.Objects;
+
+/** The OEM interface of a MenuItem, which is a button in the toolbar */
+public final class MenuItemOEMV1 {
+    private final int mKey;
+    private final String mTitle;
+    private final Drawable mIcon;
+    private final boolean mIsVisible;
+    private final boolean mIsEnabled;
+    private final boolean mIsPrimary;
+    private final boolean mIsTinted;
+    private final boolean mIsRestricted;
+    private final boolean mShowIconAndTitle;
+    private final int mDisplayBehavior;
+    private final boolean mIsCheckable;
+    private final boolean mIsChecked;
+    private final boolean mIsActivatable;
+    private final boolean mIsActivated;
+    private final Runnable mOnClickListener;
+
+    private MenuItemOEMV1(Builder builder) {
+        mKey = builder.mKey;
+        mTitle = builder.mTitle;
+        mIcon = builder.mIcon;
+        mIsVisible = builder.mIsVisible;
+        mIsEnabled = builder.mIsEnabled;
+        mIsPrimary = builder.mIsPrimary;
+        mIsTinted = builder.mIsTinted;
+        mIsRestricted = builder.mIsRestricted;
+        mShowIconAndTitle = builder.mShowIconAndTitle;
+        mDisplayBehavior = builder.mDisplayBehavior;
+        mIsCheckable = builder.mIsCheckable;
+        mIsChecked = builder.mIsChecked;
+        mIsActivatable = builder.mIsActivatable;
+        mIsActivated = builder.mIsActivated;
+        mOnClickListener = builder.mOnClickListener;
+    }
+
+    /**
+     * Gets the key, which can be used to determine if a changed MenuItem is the same as
+     * a previous MenuItem or not.
+     */
+    public int getKey() {
+        return mKey;
+    }
+
+    /** Gets the title of this MenuItem. */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /** Gets the current Icon */
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns whether the MenuItem is visible. This is somewhat of a redundant property, as
+     * the MenuItem could've been excluded from the list passed to
+     * {@link ToolbarControllerOEMV1#setMenuItems}, but having it makes it easier to diff
+     * the old and new list of MenuItems to provide efficient updates.
+     */
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
+    /**
+     * Returns whether the MenuItem is enabled. A disabled MenuItem should look visually
+     * different and not send click events to its onClickListener.
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * Returns if this MenuItem is a primary one, which should be visually different.
+     */
+    public boolean isPrimary() {
+        return mIsPrimary;
+    }
+
+    /** Whether or not to tint the Icon to match the theme of the toolbar. */
+    public boolean isTinted() {
+        return mIsTinted;
+    }
+
+    /**
+     * Returns if this MenuItem is restricted due to the current driving restrictions and driving
+     * state. It should be displayed visually distinctly to indicate that. OnClick events should
+     * still be passed to the onClickListener.
+     */
+    public boolean isRestricted() {
+        return mIsRestricted;
+    }
+
+    /**
+     * Returns if both the icon and title should be shown. If not, and they're both provided,
+     * only the icon will be shown and the title will be used as a content description.
+     */
+    public boolean isShowingIconAndTitle() {
+        return mShowIconAndTitle;
+    }
+
+    /** Always show the MenuItem on the toolbar */
+    public static final int DISPLAY_BEHAVIOR_ALWAYS = 0;
+    /** Show the MenuItem in the toolbar if there's space, otherwise show it in the overflow menu */
+    public static final int DISPLAY_BEHAVIOR_IF_ROOM = 1;
+    /** Never show the MenuItem on the toolbar, always put it in an overflow menu */
+    public static final int DISPLAY_BEHAVIOR_NEVER = 2;
+
+    /**
+     * Gets the current display behavior.
+     *
+     * See {@link #DISPLAY_BEHAVIOR_ALWAYS}, {@link #DISPLAY_BEHAVIOR_IF_ROOM}, and
+     * {@link #DISPLAY_BEHAVIOR_NEVER}.
+     */
+    public int getDisplayBehavior() {
+        return mDisplayBehavior;
+    }
+
+    /** Returns whether the MenuItem is checkable. If it is, it will be displayed as a switch. */
+    public boolean isCheckable() {
+        return mIsCheckable;
+    }
+
+    /**
+     * Returns whether the MenuItem is currently checked. Only valid if {@link #isCheckable()}
+     * is true.
+     */
+    public boolean isChecked() {
+        return mIsChecked;
+    }
+
+    /**
+     * Returns whether the MenuItem is activatable. If it is, it's every click will toggle
+     * the MenuItem's View to appear activated or not.
+     */
+    public boolean isActivatable() {
+        return mIsActivatable;
+    }
+
+    /**
+     * Returns whether or not this view is activated. Toggles after every click if
+     * {@link #isActivatable} is true.
+     */
+    public boolean isActivated() {
+        return mIsActivated;
+    }
+
+    /**
+     * Returns the onClickListener for this MenuItem. The plugin must not call this
+     * when the MenuItem is disabled, but still should call it when it's restricted.
+     */
+    public Runnable getOnClickListener() {
+        return mOnClickListener;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof MenuItemOEMV1)) return false;
+        MenuItemOEMV1 that = (MenuItemOEMV1) o;
+        return mKey == that.mKey
+                && mIsVisible == that.mIsVisible
+                && mIsEnabled == that.mIsEnabled
+                && mIsPrimary == that.mIsPrimary
+                && mIsTinted == that.mIsTinted
+                && mIsRestricted == that.mIsRestricted
+                && mShowIconAndTitle == that.mShowIconAndTitle
+                && mDisplayBehavior == that.mDisplayBehavior
+                && mIsCheckable == that.mIsCheckable
+                && mIsChecked == that.mIsChecked
+                && mIsActivatable == that.mIsActivatable
+                && mIsActivated == that.mIsActivated
+                && Objects.equals(mTitle, that.mTitle)
+                && Objects.equals(mIcon, that.mIcon)
+                && mOnClickListener == that.mOnClickListener;
+    }
+
+    @Override
+    public int hashCode() {
+        // Exclude mOnClickListener from the hashcode because it must be exactly the
+        // same object. We don't want a Runnable subclass to be able to override hashCode()
+        // and then break the contract that two equal objects must produce the same hashcode.
+        return Objects.hash(mKey, mTitle, mIcon, mIsVisible, mIsEnabled, mIsPrimary, mIsTinted,
+                mIsRestricted, mShowIconAndTitle, mDisplayBehavior, mIsCheckable, mIsChecked,
+                mIsActivatable, mIsActivated);
+    }
+
+    /** A builder class for {@link MenuItemOEMV1} */
+    public static final class Builder {
+        private int mKey = 0;
+        private String mTitle;
+        private Drawable mIcon;
+        private boolean mIsVisible = true;
+        private boolean mIsEnabled = true;
+        private boolean mIsPrimary = false;
+        private boolean mIsTinted = true;
+        private boolean mIsRestricted = false;
+        private boolean mShowIconAndTitle = false;
+        private int mDisplayBehavior = MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS;
+        private boolean mIsCheckable = false;
+        private boolean mIsChecked = false;
+        private boolean mIsActivatable = false;
+        private boolean mIsActivated = false;
+        private Runnable mOnClickListener;
+
+        private Builder() {
+        }
+
+        private Builder(MenuItemOEMV1 toCopy) {
+            mKey = toCopy.mKey;
+            mTitle = toCopy.mTitle;
+            mIcon = toCopy.mIcon;
+            mIsVisible = toCopy.mIsVisible;
+            mIsEnabled = toCopy.mIsEnabled;
+            mIsPrimary = toCopy.mIsPrimary;
+            mIsTinted = toCopy.mIsTinted;
+            mIsRestricted = toCopy.mIsRestricted;
+            mShowIconAndTitle = toCopy.mShowIconAndTitle;
+            mDisplayBehavior = toCopy.mDisplayBehavior;
+            mIsCheckable = toCopy.mIsCheckable;
+            mIsChecked = toCopy.mIsChecked;
+            mIsActivatable = toCopy.mIsActivatable;
+            mIsActivated = toCopy.mIsActivated;
+            mOnClickListener = toCopy.mOnClickListener;
+        }
+
+        /** {@link MenuItemOEMV1#getKey()} */
+        public Builder setKey(int key) {
+            mKey = key;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#getTitle()} */
+        public Builder setTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#getIcon()} */
+        public Builder setIcon(Drawable icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isVisible()} */
+        public Builder setVisible(boolean visible) {
+            mIsVisible = visible;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isEnabled()} */
+        public Builder setEnabled(boolean enabled) {
+            mIsEnabled = enabled;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isPrimary()} */
+        public Builder setPrimary(boolean primary) {
+            mIsPrimary = primary;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isTinted()} */
+        public Builder setTinted(boolean tinted) {
+            mIsTinted = tinted;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isRestricted()} */
+        public Builder setRestricted(boolean restricted) {
+            mIsRestricted = restricted;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isShowingIconAndTitle()} */
+        public Builder setShowIconAndTitle(boolean showIconAndTitle) {
+            mShowIconAndTitle = showIconAndTitle;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#getDisplayBehavior()} */
+        public Builder setDisplayBehavior(int displayBehavior) {
+            if (displayBehavior != MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS
+                    && displayBehavior != MenuItemOEMV1.DISPLAY_BEHAVIOR_IF_ROOM
+                    && displayBehavior != MenuItemOEMV1.DISPLAY_BEHAVIOR_NEVER) {
+                throw new IllegalArgumentException("Invalid display behavior!");
+            }
+            mDisplayBehavior = displayBehavior;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isCheckable()} */
+        public Builder setCheckable(boolean checkable) {
+            mIsCheckable = checkable;
+            if (!mIsCheckable) {
+                mIsChecked = false;
+            }
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isChecked()} */
+        public Builder setChecked(boolean checked) {
+            if (!mIsCheckable) {
+                throw new IllegalStateException("MenuItem must be checkable to be checked");
+            }
+            mIsChecked = checked;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isActivatable()} */
+        public Builder setActivatable(boolean activatable) {
+            mIsActivatable = activatable;
+            if (!mIsActivatable) {
+                mIsActivated = false;
+            }
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#isActivated()} */
+        public Builder setActivated(boolean activated) {
+            if (!mIsActivatable) {
+                throw new IllegalStateException("MenuItem must be activatable to be activated");
+            }
+            mIsActivated = activated;
+            return this;
+        }
+
+        /** {@link MenuItemOEMV1#getOnClickListener()} */
+        public Builder setOnClickListener(Runnable onClickListener) {
+            mOnClickListener = onClickListener;
+            return this;
+        }
+
+        /** Builds the final {@link MenuItemOEMV1} */
+        public MenuItemOEMV1 build() {
+            return new MenuItemOEMV1(this);
+        }
+    }
+
+    /**
+     * Creates a new {@link Builder}.
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Creates a new {@link Builder} that is initialized with all the properties of the current
+     * MenuItem. This can be used for creating an altered copy of the MenuItem easily.
+     */
+    public Builder copy() {
+        return new Builder(this);
+    }
+}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ProgressBarControllerOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ProgressBarControllerOEMV1.java
similarity index 95%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ProgressBarControllerOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ProgressBarControllerOEMV1.java
index 3d76209..8d71070 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ProgressBarControllerOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ProgressBarControllerOEMV1.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.toolbar;
+package com.android.car.ui.plugin.oemapis.toolbar;
 
 /**
  * Interface for a Progress Bar. It's methods are a subset of the methods of
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/TabOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/TabOEMV1.java
similarity index 97%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/TabOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/TabOEMV1.java
index d175aca..bbfef48 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/TabOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/TabOEMV1.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.toolbar;
+package com.android.car.ui.plugin.oemapis.toolbar;
 
 import android.graphics.drawable.Drawable;
 
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ToolbarControllerOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ToolbarControllerOEMV1.java
similarity index 92%
rename from car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ToolbarControllerOEMV1.java
rename to car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ToolbarControllerOEMV1.java
index d50382c..9dbe98d 100644
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/ToolbarControllerOEMV1.java
+++ b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/plugin/oemapis/toolbar/ToolbarControllerOEMV1.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.car.ui.sharedlibrary.oemapis.toolbar;
+package com.android.car.ui.plugin.oemapis.toolbar;
 
 import android.graphics.drawable.Drawable;
 
@@ -46,7 +46,7 @@
      * @param tabs Nullable. Must not be mutated. List of tabs to show.
      * @param selectedTab The index of the tab that is initially selected.
      */
-    void setTabs(List<? extends TabOEMV1> tabs, int selectedTab);
+    void setTabs(List<TabOEMV1> tabs, int selectedTab);
 
     /**
      * Selects a tab added to this toolbar. See
@@ -119,7 +119,7 @@
     /**
      * Sets the {@link MenuItemOEMV1 Menuitems} to display.
      */
-    void setMenuItems(List<? extends MenuItemOEMV1> items);
+    void setMenuItems(List<MenuItemOEMV1> items);
 
     /**
      * Sets a {@link Consumer<String>} to be called whenever the text in the search box
@@ -147,4 +147,10 @@
 
     /** Gets a {@link ProgressBarControllerOEMV1 ProgressBarController} */
     ProgressBarControllerOEMV1 getProgressBar();
+
+    /**
+     * This is a hint that indicates the app would like the toolbar drawn without a background,
+     * so that the toolbar blends in with the app more.
+     */
+    void setBackgroundShown(boolean shown);
 }
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryVersionProviderOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryVersionProviderOEMV1.java
deleted file mode 100644
index 4b01d63..0000000
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/SharedLibraryVersionProviderOEMV1.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrary.oemapis;
-
-import android.content.Context;
-
-/**
- * An interface for objects that support providing a list a supported versions of
- * {@link SharedLibraryFactoryOEMV1} to the app. See {@link #getSharedLibraryFactory(int)}}
- * for more information.
- */
-public interface SharedLibraryVersionProviderOEMV1 {
-    /**
-     * Returns an object that implements {@link SharedLibraryFactoryOEMV1} or a later version.
-     *
-     * OEMs should aim to return the highest version of the factory possible that is <=
-     * {@code maxVersion}. If the shared library is not able to provide that version,
-     * it may return null, in which case car-ui-lib will fall back to it's static,
-     * uncustomized implementation.
-     *
-     * The shared library may also choose to return different SharedLibraryFactories based on
-     * certain conditions, like what type of device this is, or what app it's being used in.
-     * (The app can be discovered via {@link android.app.Application#getProcessName()}
-     *
-     * @param maxVersion The maximum version of {@link SharedLibraryFactoryOEMV1} supported by the
-     *                   app.
-     * @param context The shared library's context. It uses the shared library's classloader,
-     *                so layout inflaters created from it can use views defined in the shared lib.
-     * @param packageName The package name of the app creating the shared library. Can be used
-     *                    to provide per-app customizations.
-     *
-     * @return An object implementing {@link SharedLibraryFactoryOEMV1} for a version <=
-     *         {@code maxVersion}.
-     */
-    Object getSharedLibraryFactory(int maxVersion, Context context, String packageName);
-}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterOEMV1.java
deleted file mode 100644
index 2e4156e..0000000
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/AdapterOEMV1.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrary.oemapis.recyclerview;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * See {@link androidx.recyclerview.widget.RecyclerView.Adapter}
- *
- * @param <V> A class that extends ViewHolder that will be used by the adapter.
- */
-public interface AdapterOEMV1<V extends ViewHolderOEMV1> {
-
-    int ALLOW = 0;
-    int PREVENT_WHEN_EMPTY = 1;
-    int PREVENT = 2;
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemCount()} */
-    int getItemCount();
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemId(int)} */
-    long getItemId(int position);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getItemViewType(int)} */
-    int getItemViewType(int position);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#getStateRestorationPolicy()} */
-    int getStateRestorationPolicyInt();
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onAttachedToRecyclerView} */
-    void onAttachedToRecyclerView(RecyclerViewOEMV1 recyclerView);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#bindViewHolder} */
-    void bindViewHolder(V holder, int position);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#createViewHolder} */
-    V createViewHolder(ViewGroup parent, int viewType);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onDetachedFromRecyclerView} */
-    void onDetachedFromRecyclerView(RecyclerViewOEMV1 recyclerView);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onFailedToRecycleView} */
-    boolean onFailedToRecycleView(V holder);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewAttachedToWindow} */
-    void onViewAttachedToWindow(V holder);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewDetachedFromWindow} */
-    void onViewDetachedFromWindow(V holder);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#onViewRecycled} */
-    void onViewRecycled(V holder);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#registerAdapterDataObserver} */
-    void registerAdapterDataObserver(AdapterDataObserverOEMV1 observer);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#unregisterAdapterDataObserver} */
-    void unregisterAdapterDataObserver(AdapterDataObserverOEMV1 observer);
-
-    /** See {@link androidx.recyclerview.widget.RecyclerView.Adapter#hasStableIds} */
-    boolean hasStableIds();
-
-    /**
-     * Sets the wrapping recyclerview
-     * @param recyclerview the wrapping recyclerview
-     */
-    void setRecyclerView(View recyclerview);
-}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java
deleted file mode 100644
index bf7c2bf..0000000
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewAttributesOEMV1.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrary.oemapis.recyclerview;
-
-/**
- * Set of attributes passed from UI/layout to the oem implementation
- */
-public interface RecyclerViewAttributesOEMV1 {
-
-    int SIZE_SMALL = 0;
-    int SIZE_MEDIUM = 1;
-    int SIZE_LARGE = 2;
-
-    /** Returns if rotary scroll is enabled */
-    boolean isRotaryScrollEnabled();
-
-    /** Describes the expected relative size of the
-     * {@link androidx.recyclerview.widget.RecyclerView}. The list may be rendered differently for
-     * each expected size.
-     */
-    int getSize();
-
-    /** Returns information regarding the layout style */
-    LayoutStyleOEMV1 getLayoutStyle();
-}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewOEMV1.java
deleted file mode 100644
index a37be2c..0000000
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/recyclerview/RecyclerViewOEMV1.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrary.oemapis.recyclerview;
-
-import android.view.View;
-
-/**
- * {@link androidx.recyclerview.widget.RecyclerView}
- */
-public interface RecyclerViewOEMV1 {
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#setAdapter(Adapter)} */
-    void setAdapter(AdapterOEMV1 adapter);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#addOnScrollListener} */
-    void addOnScrollListener(OnScrollListenerOEMV1 listener);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#removeOnScrollListener} */
-    void removeOnScrollListener(OnScrollListenerOEMV1 listener);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#clearOnScrollListeners()} */
-    void clearOnScrollListeners();
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#scrollToPosition(int)} */
-    void scrollToPosition(int position);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#smoothScrollBy(int, int)} */
-    void smoothScrollBy(int dx, int dy);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#smoothScrollToPosition(int)} */
-    void smoothScrollToPosition(int position);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#setHasFixedSize(boolean)} */
-    void setHasFixedSize(boolean hasFixedSize);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#hasFixedSize()} */
-    boolean hasFixedSize();
-
-    /**
-     * set {@link LayoutStyleOEMV1}. This is the replacement for
-     * {@link androidx.recyclerview.widget.RecyclerView.LayoutManager}
-     */
-    void setLayoutStyle(LayoutStyleOEMV1 layoutStyle);
-
-    /**
-     * Returns the view that will be displayed on the screen.
-     */
-    View getView();
-
-    /** {@link android.view.View#setPadding(int, int, int, int)} */
-    void setPadding(int left, int top, int right, int bottom);
-
-    /** {@link android.view.View#setPaddingRelative(int, int, int, int)} */
-    void setPaddingRelative(int start, int top, int end, int bottom);
-
-    /** {@link androidx.recyclerview.widget.RecyclerView#setClipToPadding(boolean)} */
-    void setClipToPadding(boolean clipToPadding);
-
-    /**
-     * Return's the container which contains the scrollbar and this RecyclerView.
-     */
-    View getContainer();
-}
diff --git a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/MenuItemOEMV1.java b/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/MenuItemOEMV1.java
deleted file mode 100644
index 2fb99b9..0000000
--- a/car-ui-lib/oem-apis/src/main/java/com/android/car/ui/sharedlibrary/oemapis/toolbar/MenuItemOEMV1.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2020 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.ui.sharedlibrary.oemapis.toolbar;
-
-import android.graphics.drawable.Drawable;
-
-import java.util.function.Consumer;
-
-/** The OEM interface of a MenuItem, which is a button in the toolbar */
-@SuppressWarnings("AndroidJdkLibsChecker")
-public interface MenuItemOEMV1 {
-
-    /** Sets a listener that will be called when any property of the MenuItem changes */
-    void setUpdateListener(Consumer<MenuItemOEMV1> listener);
-
-    /**
-     * Triggers the MenuItem being clicked. This will toggle it's activated/checked state
-     * if it supports those, and also call it's onClickListener.
-     */
-    void performClick();
-
-    /** Gets the id, which is purely for the client to distinguish MenuItems with. */
-    int getId();
-
-    /** Returns whether the MenuItem is enabled */
-    boolean isEnabled();
-
-    /** Returns whether the MenuItem is checkable. If it is, it will be displayed as a switch. */
-    boolean isCheckable();
-
-    /**
-     * Returns whether the MenuItem is currently checked. Only valid if {@link #isCheckable()}
-     * is true.
-     */
-    boolean isChecked();
-
-    /** Whether or not to tint the Icon to match the theme of the toolbar */
-    boolean isTinted();
-
-    /** Returns whether or not the MenuItem is visible */
-    boolean isVisible();
-
-    /**
-     * Returns whether the MenuItem is activatable. If it is, it's every click will toggle
-     * the MenuItem's View to appear activated or not.
-     */
-    boolean isActivatable();
-
-    /** Returns whether or not this view is selected. Toggles after every click */
-    boolean isActivated();
-
-    /** Gets the title of this MenuItem. */
-    String getTitle();
-
-    /**
-     * Returns if this MenuItem is restricted due to the current driving restrictions and driving
-     * state. It should be displayed visually distinctly to indicate that.
-     */
-    boolean isRestricted();
-
-    /**
-     * Returns if both the icon and title should be shown. If not, and they're both provided,
-     * only the icon will be shown and the title will be used as a content description.
-     */
-    boolean isShowingIconAndTitle();
-
-    /**
-     * Returns if the MenuItem should do something when clicked. This can be used to forgo
-     * setting an onClickListener on it's View when it's not clickable.
-     */
-    boolean isClickable();
-
-    /** Always show the MenuItem on the toolbar */
-    int DISPLAY_BEHAVIOR_ALWAYS = 0;
-    /** Show the MenuItem in the toolbar if there's space, otherwise show it in the overflow menu */
-    int DISPLAY_BEHAVIOR_IF_ROOM = 1;
-    /** Never show the MenuItem on the toolbar, always put it in an overflow menu */
-    int DISPLAY_BEHAVIOR_NEVER = 2;
-
-    /**
-     * Gets the current display behavior.
-     *
-     * See {@link #DISPLAY_BEHAVIOR_ALWAYS}, {@link #DISPLAY_BEHAVIOR_IF_ROOM}, and
-     * {@link #DISPLAY_BEHAVIOR_NEVER}.
-     */
-    int getDisplayBehavior();
-
-    /** Gets the current Icon */
-    Drawable getIcon();
-
-    /**
-     * Returns if this MenuItem is a primary one, which should be visually different.
-     *
-     * This value will not change, even after an update was triggered.
-     */
-    boolean isPrimary();
-}
diff --git a/car-ui-lib/paintbooth/build.gradle b/car-ui-lib/paintbooth/build.gradle
index eba3fbe..66bf330 100644
--- a/car-ui-lib/paintbooth/build.gradle
+++ b/car-ui-lib/paintbooth/build.gradle
@@ -50,6 +50,6 @@
     implementation project(':car-ui-lib')
     debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
     api 'androidx.annotation:annotation:1.2.0'
-    api 'androidx.constraintlayout:constraintlayout:2.0.4'
-    api 'androidx.recyclerview:recyclerview:1.2.0'
+    api 'androidx.constraintlayout:constraintlayout:2.1.0'
+    api 'androidx.recyclerview:recyclerview:1.2.1'
 }
diff --git a/car-ui-lib/paintbooth/src/main/AndroidManifest.xml b/car-ui-lib/paintbooth/src/main/AndroidManifest.xml
index c6d2e89..9b5da9b 100644
--- a/car-ui-lib/paintbooth/src/main/AndroidManifest.xml
+++ b/car-ui-lib/paintbooth/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.car.ui.paintbooth">
+  <uses-sdk
+      android:minSdkVersion="28" />
   <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
   <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
   <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
index 6b1b8f7..8220b30 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/MainActivity.java
@@ -17,7 +17,7 @@
 package com.android.car.ui.paintbooth;
 
 import static com.android.car.ui.paintbooth.PaintBoothApplication.SHARED_PREFERENCES_FILE;
-import static com.android.car.ui.paintbooth.PaintBoothApplication.SHARED_PREFERENCES_SHARED_LIB_DENYLIST;
+import static com.android.car.ui.paintbooth.PaintBoothApplication.SHARED_PREFERENCES_PLUGIN_DENYLIST;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -78,8 +78,8 @@
     private final List<ListElement> mActivities = Arrays.asList(
             new ServiceElement("Show foreground activities", CurrentActivityService.class),
             new ServiceElement("Simulate Screen Bounds", VisibleBoundsSimulator.class),
-            new SwitchElement("Enable shared library", this::isSharedLibEnabled,
-                    this::onSharedLibSwitchChanged),
+            new SwitchElement("Add PaintBooth to plugin deny-list", this::isInPluginDenyList,
+                    this::onPluginSwitchChanged),
             new ActivityElement("Dialogs sample", DialogsActivity.class),
             new ActivityElement("App Styled View Modal", AppStyledViewSampleActivity.class),
             new ActivityElement("List sample", CarUiRecyclerViewActivity.class),
@@ -287,16 +287,16 @@
         startForegroundService(intent);
     }
 
-    private boolean isSharedLibEnabled() {
+    private boolean isInPluginDenyList() {
         return getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
-                .getStringSet(SHARED_PREFERENCES_SHARED_LIB_DENYLIST, null) == null;
+                .getStringSet(SHARED_PREFERENCES_PLUGIN_DENYLIST, null) != null;
     }
 
-    private void onSharedLibSwitchChanged(CompoundButton unused, boolean checked) {
+    private void onPluginSwitchChanged(CompoundButton unused, boolean checked) {
         getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
                 .edit()
-                .putStringSet(SHARED_PREFERENCES_SHARED_LIB_DENYLIST,
-                        checked ? null : Collections.singleton("com.chassis.car.ui.sharedlibrary"))
+                .putStringSet(SHARED_PREFERENCES_PLUGIN_DENYLIST,
+                        checked ? Collections.singleton("com.chassis.car.ui.plugin") : null)
                 .apply();
         Toast.makeText(this, "Relaunch PaintBooth to see effects", Toast.LENGTH_SHORT).show();
     }
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/PaintBoothApplication.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/PaintBoothApplication.java
index 2b11038..819db30 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/PaintBoothApplication.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/PaintBoothApplication.java
@@ -18,29 +18,29 @@
 import android.app.Application;
 import android.content.Context;
 
-import com.android.car.ui.sharedlibrarysupport.SharedLibraryConfigProvider;
-import com.android.car.ui.sharedlibrarysupport.SharedLibrarySpecifier;
+import com.android.car.ui.pluginsupport.PluginConfigProvider;
+import com.android.car.ui.pluginsupport.PluginSpecifier;
 
 import java.util.Collections;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
- * A {@link Application} subclass that implements {@link SharedLibraryConfigProvider},
- * allowing PaintBooth to disable the shared library.
+ * A {@link Application} subclass that implements {@link PluginConfigProvider},
+ * allowing PaintBooth to disable the plugin.
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
-public class PaintBoothApplication extends Application implements SharedLibraryConfigProvider {
+public class PaintBoothApplication extends Application implements PluginConfigProvider {
     public static final String SHARED_PREFERENCES_FILE = "paintbooth_shared_prefs";
-    public static final String SHARED_PREFERENCES_SHARED_LIB_DENYLIST =
-            "paintbooth_shared_lib_deny";
+    public static final String SHARED_PREFERENCES_PLUGIN_DENYLIST =
+            "paintbooth_plugin_deny";
 
     @Override
-    public Set<SharedLibrarySpecifier> getSharedLibraryDenyList() {
+    public Set<PluginSpecifier> getPluginDenyList() {
         return getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
-                .getStringSet(SHARED_PREFERENCES_SHARED_LIB_DENYLIST, Collections.emptySet())
+                .getStringSet(SHARED_PREFERENCES_PLUGIN_DENYLIST, Collections.emptySet())
                 .stream()
-                .map(packageName -> SharedLibrarySpecifier.builder()
+                .map(packageName -> PluginSpecifier.builder()
                         .setPackageName(packageName)
                         .build())
                 .collect(Collectors.toSet());
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/appstyledview/AppStyledViewSampleActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/appstyledview/AppStyledViewSampleActivity.java
index d4609a3..d88cac3 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/appstyledview/AppStyledViewSampleActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/appstyledview/AppStyledViewSampleActivity.java
@@ -39,6 +39,8 @@
  */
 public class AppStyledViewSampleActivity extends AppCompatActivity {
 
+    private AppStyledDialogController mAppStyledDialogController;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -51,8 +53,8 @@
         toolbar.setNavButtonMode(NavButtonMode.BACK);
         toolbar.setLogo(R.drawable.ic_launcher);
 
-        AppStyledDialogController controller = new AppStyledDialogController(this);
-        int width = controller.getAppStyledViewDialogWidth();
+        mAppStyledDialogController = new AppStyledDialogController(this);
+        int width = mAppStyledDialogController.getAppStyledViewDialogWidth();
 
         Resources resources = getResources();
         Configuration config = resources.getConfiguration();
@@ -70,9 +72,17 @@
 
         Button btn = findViewById(R.id.show_app_styled_fragment);
         btn.setOnClickListener(v -> {
-            controller.setContentView(appStyledTestView);
-            controller.setNavIcon(AppStyledViewNavIcon.CLOSE);
-            controller.show();
+            mAppStyledDialogController.setContentView(appStyledTestView);
+            mAppStyledDialogController.setNavIcon(AppStyledViewNavIcon.CLOSE);
+            mAppStyledDialogController.show();
         });
     }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mAppStyledDialogController != null) {
+            mAppStyledDialogController.dismiss();
+        }
+    }
 }
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
index 28d2874..090e2af 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/CarUiRecyclerViewActivity.java
@@ -51,7 +51,7 @@
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 
-        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateSampleData());
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, generateSampleData());
         recyclerView.setAdapter(adapter);
     }
 
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
index ee08d25..2a3d236 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/GridCarUiRecyclerViewActivity.java
@@ -49,7 +49,7 @@
 
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
 
-        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateSampleData());
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, generateSampleData());
         recyclerView.setAdapter(adapter);
     }
 
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
index fbfd49f..2bcb3bf 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/caruirecyclerview/RecyclerViewAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.car.ui.paintbooth.caruirecyclerview;
 
+import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,16 +35,18 @@
 public class RecyclerViewAdapter extends
         RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> {
 
+    private final Context mContext;
     private List<String> mData;
 
-    public RecyclerViewAdapter(List<String> data) {
+    public RecyclerViewAdapter(Context context, List<String> data) {
+        this.mContext = context;
         this.mData = data;
     }
 
     @NonNull
     @Override
     public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        LayoutInflater inflator = LayoutInflater.from(parent.getContext());
+        LayoutInflater inflator = LayoutInflater.from(mContext);
         View view = inflator.inflate(R.layout.car_ui_recycler_view_list_item, parent, false);
         return new RecyclerViewHolder(view);
     }
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
index bf62cc7..c7304b0 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/dialogs/DialogsActivity.java
@@ -89,6 +89,10 @@
                 v -> showDialogWithSingleChoiceItems()));
         mButtons.add(Pair.create(R.string.dialog_show_list_items_without_default_button,
                 v -> showDialogWithListItemsWithoutDefaultButton()));
+        mButtons.add(Pair.create(R.string.dialog_show_unfocusable_list_items,
+                v -> showDialogWithUnfocusableListItems()));
+        mButtons.add(Pair.create(R.string.dialog_show_empty_list_items,
+                v -> showDialogWithEmptyList()));
         mButtons.add(Pair.create(R.string.dialog_show_permission_dialog,
                 v -> showPermissionDialog()));
         mButtons.add(Pair.create(R.string.dialog_show_multi_permission_dialog,
@@ -236,6 +240,50 @@
                 .show();
     }
 
+    private void showDialogWithUnfocusableListItems() {
+        List<CarUiContentListItem> data = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("First item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Second item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Third item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Fourth item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Fifth item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Sixth item");
+        data.add(item);
+
+        item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Seventh item");
+        data.add(item);
+
+        new AlertDialogBuilder(this)
+                .setTitle("Unfocusable items")
+                .setAdapter(new CarUiListItemAdapter(data))
+                .show();
+    }
+
+    private void showDialogWithEmptyList() {
+        new AlertDialogBuilder(this)
+                .setTitle("Empty list")
+                .setAdapter(new CarUiListItemAdapter(new ArrayList<CarUiContentListItem>()))
+                .show();
+    }
+
     private void showDialogWithSubtitleAndIcon() {
         new AlertDialogBuilder(this)
                 .setTitle(R.string.my_title)
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
index 5b4b2a2..068a363 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/toolbar/ToolbarActivity.java
@@ -108,12 +108,12 @@
         }));
 
         mButtons.add(Pair.create(getString(R.string.toolbar_cycle_nav_button), v -> {
-            Toolbar.NavButtonMode mode = toolbar.getNavButtonMode();
-            if (mode == Toolbar.NavButtonMode.DISABLED) {
+            NavButtonMode mode = toolbar.getNavButtonMode();
+            if (mode == NavButtonMode.DISABLED) {
                 toolbar.setNavButtonMode(NavButtonMode.BACK);
-            } else if (mode == Toolbar.NavButtonMode.BACK) {
+            } else if (mode == NavButtonMode.BACK) {
                 toolbar.setNavButtonMode(NavButtonMode.CLOSE);
-            } else if (mode == Toolbar.NavButtonMode.CLOSE) {
+            } else if (mode == NavButtonMode.CLOSE) {
                 toolbar.setNavButtonMode(NavButtonMode.DOWN);
             } else {
                 toolbar.setNavButtonMode(NavButtonMode.DISABLED);
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/EdiTextWithPrivateImeCommand.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/EdiTextWithPrivateImeCommand.java
new file mode 100644
index 0000000..3298ee4
--- /dev/null
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/EdiTextWithPrivateImeCommand.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.ui.paintbooth.widescreenime;
+
+import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_CLEAR_DATA_ACTION;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/**
+ * Edit text supporting the callbacks from the IMS.
+ */
+public class EdiTextWithPrivateImeCommand extends EditText {
+
+    public EdiTextWithPrivateImeCommand(Context context) {
+        super(context);
+    }
+
+    public EdiTextWithPrivateImeCommand(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public EdiTextWithPrivateImeCommand(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public EdiTextWithPrivateImeCommand(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean onPrivateIMECommand(String action, Bundle data) {
+        if (WIDE_SCREEN_CLEAR_DATA_ACTION.equals(action)) {
+            setText("");
+        }
+        return false;
+    }
+}
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
index f056ced..22c819c 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenImeActivity.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
@@ -130,6 +131,11 @@
                                 .setSearchResultItems(mSearchItems)
                                 .setSearchResultsInputViewIcon(ContextCompat.getDrawable(
                                         this, R.drawable.car_ui_icon_search))
+                                .setOnBackClickedListener(() -> {
+                                    Toast.makeText(this, "Back clicked on IME!",
+                                            Toast.LENGTH_SHORT).show();
+                                    Log.i(TAG, "Back clicked on IME!");
+                                })
                                 .build());
                     }
                 })
@@ -137,16 +143,20 @@
 
         toolbar.setMenuItems(mMenuItems);
 
-        mWidescreenItems.add(new EditTextElement("Default Input Edit Text field", null));
+        mWidescreenItems.add(new EditTextElement("Default Input Edit Text field", null, null));
         mWidescreenItems.add(
-                new EditTextElement("Add Desc to content area", this::addDescToContentArea));
-        mWidescreenItems.add(new EditTextElement("Hide the content area", this::hideContentArea));
-        mWidescreenItems.add(new EditTextElement("Hide extraction view", this::hideExtractionView));
+                new EditTextElement("Add Desc to content area", this::addDescToContentArea,
+                        this::addDescToContentArea));
+        mWidescreenItems.add(new EditTextElement("Hide the content area", this::hideContentArea,
+                this::hideContentArea));
+        mWidescreenItems.add(new EditTextElement("Hide extraction view", this::hideExtractionView,
+                this::hideExtractionView));
 
         mWidescreenItems.add(
-                new EditTextElement("Add icon to extracted view", this::addIconToExtractedView));
+                new EditTextElement("Add icon to extracted view", this::addIconToExtractedView,
+                        this::addIconToExtractedView));
         mWidescreenItems.add(new EditTextElement("Add error message to content area",
-                this::addErrorDescToContentArea));
+                this::addErrorDescToContentArea, this::addErrorDescToContentArea));
 
         CarUiRecyclerView recyclerView = requireViewById(R.id.list);
         recyclerView.setAdapter(mAdapter);
@@ -156,7 +166,10 @@
         if (!hasFocus) {
             return;
         }
+        addIconToExtractedView(view);
+    }
 
+    private void addIconToExtractedView(View view) {
         Bundle bundle = new Bundle();
         bundle.putInt(WIDE_SCREEN_EXTRACTED_TEXT_ICON_RES_ID, R.drawable.car_ui_icon_edit);
         mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
@@ -167,6 +180,10 @@
             return;
         }
 
+        addErrorDescToContentArea(view);
+    }
+
+    private void addErrorDescToContentArea(View view) {
         Bundle bundle = new Bundle();
         bundle.putString(ADD_ERROR_DESC_TO_INPUT_AREA, "Some error message");
         bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title");
@@ -179,6 +196,10 @@
             return;
         }
 
+        hideExtractionView(view);
+    }
+
+    private void hideExtractionView(View view) {
         EditText editText = (EditText) view;
         editText.setImeOptions(IME_FLAG_NO_EXTRACT_UI);
 
@@ -191,7 +212,10 @@
         if (!hasFocus) {
             return;
         }
+        addDescToContentArea(view);
+    }
 
+    private void addDescToContentArea(View view) {
         Bundle bundle = new Bundle();
         bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, "Title");
         bundle.putString(ADD_DESC_TO_CONTENT_AREA, "Description provided by the application");
@@ -203,6 +227,10 @@
             return;
         }
 
+        hideContentArea(view);
+    }
+
+    private void hideContentArea(View view) {
         Bundle bundle = new Bundle();
         bundle.putBoolean(REQUEST_RENDER_CONTENT_AREA, false);
         mInputMethodManager.sendAppPrivateCommand(view, WIDE_SCREEN_ACTION, bundle);
@@ -219,7 +247,7 @@
 
     private static class EditTextViewHolder extends ViewHolder {
 
-        private final EditText mEditText;
+        private final EdiTextWithPrivateImeCommand mEditText;
 
         EditTextViewHolder(@NonNull View itemView) {
             super(itemView);
@@ -234,6 +262,7 @@
             EditTextElement element = (EditTextElement) e;
             mEditText.setText(element.getText());
             mEditText.setOnFocusChangeListener(element.getOnFocusChangeListener());
+            mEditText.setOnClickListener(element.getOnClickListener());
         }
     }
 
@@ -296,15 +325,22 @@
 
     private static class EditTextElement extends ListElement {
 
-        private final OnFocusChangeListener mListener;
+        private final OnFocusChangeListener mOnFocusChangeListener;
+        private final OnClickListener mOnClickListener;
 
-        EditTextElement(String text, OnFocusChangeListener listener) {
+        EditTextElement(String text, OnFocusChangeListener listener,
+                OnClickListener onClickListener) {
             super(text);
-            mListener = listener;
+            mOnFocusChangeListener = listener;
+            mOnClickListener = onClickListener;
         }
 
         OnFocusChangeListener getOnFocusChangeListener() {
-            return mListener;
+            return mOnFocusChangeListener;
+        }
+
+        OnClickListener getOnClickListener() {
+            return mOnClickListener;
         }
 
         @Override
diff --git a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenTestView.java b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenTestView.java
index edd5ae2..edb95a0 100644
--- a/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenTestView.java
+++ b/car-ui-lib/paintbooth/src/main/java/com/android/car/ui/paintbooth/widescreenime/WideScreenTestView.java
@@ -77,7 +77,7 @@
         CarUiRecyclerView recyclerView = findViewById(R.id.list);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 
-        RecyclerViewAdapter adapter = new RecyclerViewAdapter(generateSampleData());
+        RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, generateSampleData());
         recyclerView.setAdapter(adapter);
     }
 
diff --git a/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml b/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml
index a04edd7..b81e44b 100644
--- a/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml
+++ b/car-ui-lib/paintbooth/src/main/res/layout/edit_text_list_item.xml
@@ -18,7 +18,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
 
-    <EditText
+    <com.android.car.ui.paintbooth.widescreenime.EdiTextWithPrivateImeCommand
         android:id="@+id/edit_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/car-ui-lib/paintbooth/src/main/res/values/strings.xml b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
index c53050c..fbd4fbc 100644
--- a/car-ui-lib/paintbooth/src/main/res/values/strings.xml
+++ b/car-ui-lib/paintbooth/src/main/res/values/strings.xml
@@ -286,6 +286,12 @@
   <!-- Text to show a dialog with single choice items and no default button [CHAR_LIMIT=200] -->
   <string name="dialog_show_list_items_without_default_button">Show with single choice items and no default button</string>
 
+  <!-- Text to show a dialog with unfocusable items [CHAR_LIMIT=200] -->
+  <string name="dialog_show_unfocusable_list_items">Show with unfocusable items</string>
+
+  <!-- Text to show a dialog with an empty list [CHAR_LIMIT=200] -->
+  <string name="dialog_show_empty_list_items">Show with empty list</string>
+
   <!-- Text to show a permission Dialog [CHAR_LIMIT=50] -->
   <string name="dialog_show_permission_dialog">Show permission dialog</string>
 
diff --git a/car-ui-lib/referencedesign/Android.mk b/car-ui-lib/referencedesign/Android.mk
index 0d63c18..c0823fd 100644
--- a/car-ui-lib/referencedesign/Android.mk
+++ b/car-ui-lib/referencedesign/Android.mk
@@ -13,6 +13,7 @@
     com.android.car.carlauncher \
     com.android.car.home \
     com.android.car.media \
+    com.android.car.messenger \
     com.android.car.radio \
     com.android.car.calendar \
     com.android.car.companiondevicesupport \
@@ -22,6 +23,7 @@
     com.android.car.settings \
     com.android.car.voicecontrol \
     com.android.car.faceenroll \
+    com.android.car.developeroptions \
     com.android.managedprovisioning \
     com.android.settings.intelligence \
     com.google.android.apps.automotive.inputmethod \
diff --git a/car-ui-lib/referencedesign/car-ui-lib-preinstalled-packages.xml b/car-ui-lib/referencedesign/car-ui-lib-preinstalled-packages.xml
index 5c26a2c..7012bee 100644
--- a/car-ui-lib/referencedesign/car-ui-lib-preinstalled-packages.xml
+++ b/car-ui-lib/referencedesign/car-ui-lib-preinstalled-packages.xml
@@ -19,7 +19,7 @@
      Documentation at frameworks/base/data/etc/preinstalled-packages-platform.xml
 -->
 <config>
-    <install-in-user-type package="com.chassis.car.ui.sharedlibrary">
+    <install-in-user-type package="com.chassis.car.ui.plugin">
        <install-in user-type="SYSTEM"/>
        <install-in user-type="FULL"/>
        <install-in user-type="PROFILE"/>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/Android.bp b/car-ui-lib/referencedesign/plugin/Android.bp
similarity index 83%
rename from car-ui-lib/referencedesign/sharedlibrary/Android.bp
rename to car-ui-lib/referencedesign/plugin/Android.bp
index 384b741..262cbc6 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/Android.bp
+++ b/car-ui-lib/referencedesign/plugin/Android.bp
@@ -3,12 +3,12 @@
 }
 
 android_app_certificate {
-    name: "car-ui-lib-sharedlibrary-certificate",
+    name: "car-ui-lib-plugin-certificate",
     certificate: "chassis_upload_key"
 }
 
 android_app {
-    name: "car-ui-lib-sharedlibrary",
+    name: "car-ui-lib-plugin",
 
     min_sdk_version: "28",
     target_sdk_version: "30",
@@ -17,7 +17,7 @@
     manifest: "src/main/AndroidManifest.xml",
     srcs: ["src/main/java/**/*.java"],
     resource_dirs: ["src/main/res"],
-
+    libs: ["android.car-stubs"],
     static_libs: [
         "car-ui-lib-oem-apis",
         "androidx.annotation_annotation",
@@ -33,5 +33,5 @@
         enabled: false,
     },
 
-    certificate: ":car-ui-lib-sharedlibrary-certificate",
+    certificate: ":car-ui-lib-plugin-certificate",
 }
diff --git a/car-ui-lib/referencedesign/sharedlibrary/build.gradle b/car-ui-lib/referencedesign/plugin/build.gradle
similarity index 86%
rename from car-ui-lib/referencedesign/sharedlibrary/build.gradle
rename to car-ui-lib/referencedesign/plugin/build.gradle
index 99e0b1d..b9d3efd 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/build.gradle
+++ b/car-ui-lib/referencedesign/plugin/build.gradle
@@ -38,20 +38,22 @@
     // support signing apps using individual pk8 and x509.pem files, it requires a keystore file.
     signingConfigs {
         debug {
-            storeFile file('chassis_upload_key.pepk')
+            storeFile file('chassis_upload_key.jks')
             storePassword 'chassis'
             keyAlias 'chassis'
             keyPassword 'chassis'
         }
     }
+
+    useLibrary 'android.car'
 }
 
 dependencies {
     implementation project(':oem-apis')
     api 'androidx.annotation:annotation:1.2.0'
-    api 'androidx.appcompat:appcompat:1.3.0'
-    api 'androidx.constraintlayout:constraintlayout:2.0.4'
+    api 'androidx.appcompat:appcompat:1.3.1'
+    api 'androidx.constraintlayout:constraintlayout:2.1.0'
     api 'androidx.preference:preference:1.1.1'
-    api 'androidx.recyclerview:recyclerview:1.2.0'
-    api 'androidx.core:core:1.5.0'
+    api 'androidx.recyclerview:recyclerview:1.2.1'
+    api 'androidx.core:core:1.6.0'
 }
diff --git a/car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.pepk b/car-ui-lib/referencedesign/plugin/chassis_upload_key.jks
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.pepk
rename to car-ui-lib/referencedesign/plugin/chassis_upload_key.jks
Binary files differ
diff --git a/car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.pk8 b/car-ui-lib/referencedesign/plugin/chassis_upload_key.pk8
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.pk8
rename to car-ui-lib/referencedesign/plugin/chassis_upload_key.pk8
Binary files differ
diff --git a/car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.x509.pem b/car-ui-lib/referencedesign/plugin/chassis_upload_key.x509.pem
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/chassis_upload_key.x509.pem
rename to car-ui-lib/referencedesign/plugin/chassis_upload_key.x509.pem
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/AndroidManifest.xml b/car-ui-lib/referencedesign/plugin/src/main/AndroidManifest.xml
similarity index 75%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/AndroidManifest.xml
rename to car-ui-lib/referencedesign/plugin/src/main/AndroidManifest.xml
index ba07316..8ff13f1 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/AndroidManifest.xml
@@ -16,18 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.chassis.car.ui.sharedlibrary"
+    package="com.chassis.car.ui.plugin"
     android:versionName="0.0.4"
     android:versionCode="4">
 
     <uses-feature android:name="android.hardware.type.automotive" />
 
     <application>
-        <activity android:name=".MainActivity"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.car.ui.intent.action.SHARED_LIBRARY"/>
-            </intent-filter>
-        </activity>
+        <provider
+            android:name="com.android.car.ui.plugin.PluginNameProvider"
+            android:authorities="com.android.car.ui.plugin"
+            android:enabled="false"
+            android:exported="true" />
     </application>
 </manifest>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/android/car/ui/plugin/PluginVersionProviderImpl.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/android/car/ui/plugin/PluginVersionProviderImpl.java
new file mode 100644
index 0000000..ad0bd71
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/android/car/ui/plugin/PluginVersionProviderImpl.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.ui.plugin;
+
+import android.content.Context;
+
+import com.android.car.ui.plugin.oemapis.PluginVersionProviderOEMV1;
+
+import com.chassis.car.ui.plugin.PluginFactoryImpl;
+
+/**
+ * An implementation of {@link PluginVersionProviderOEMV1} for the reference design plugin.
+ */
+public class PluginVersionProviderImpl implements PluginVersionProviderOEMV1 {
+
+    @Override
+    public Object getPluginFactory(int maxVersion, Context context, String packageName) {
+        return new PluginFactoryImpl(context);
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/PluginFactoryImpl.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/PluginFactoryImpl.java
new file mode 100644
index 0000000..c44f8eb
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/PluginFactoryImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 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.chassis.car.ui.plugin;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.car.ui.plugin.oemapis.FocusAreaOEMV1;
+import com.android.car.ui.plugin.oemapis.FocusParkingViewOEMV1;
+import com.android.car.ui.plugin.oemapis.InsetsOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV2;
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
+
+import com.chassis.car.ui.plugin.recyclerview.RecyclerViewImpl;
+import com.chassis.car.ui.plugin.toolbar.BaseLayoutInstaller;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An implementation of {@link PluginFactoryImpl} for creating the reference design
+ * car-ui-lib components.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+public class PluginFactoryImpl implements PluginFactoryOEMV2 {
+
+    private final Context mPluginContext;
+    @Nullable
+    private Function<Context, FocusParkingViewOEMV1> mFocusParkingViewFactory;
+    @Nullable
+    private Function<Context, FocusAreaOEMV1> mFocusAreaFactory;
+
+    public PluginFactoryImpl(Context pluginContext) {
+        mPluginContext = pluginContext;
+    }
+
+    @Override
+    public void setRotaryFactories(
+            Function<Context, FocusParkingViewOEMV1> focusParkingViewFactory,
+            Function<Context, FocusAreaOEMV1> focusAreaFactory) {
+        mFocusParkingViewFactory = focusParkingViewFactory;
+        mFocusAreaFactory = focusAreaFactory;
+    }
+
+    @Override
+    public ToolbarControllerOEMV1 installBaseLayoutAround(
+            Context sourceContext,
+            View contentView,
+            Consumer<InsetsOEMV1> insetsChangedListener,
+            boolean toolbarEnabled,
+            boolean fullscreen) {
+
+        return BaseLayoutInstaller.installBaseLayoutAround(
+                sourceContext,
+                mPluginContext,
+                contentView,
+                insetsChangedListener,
+                toolbarEnabled,
+                fullscreen,
+                mFocusParkingViewFactory,
+                mFocusAreaFactory);
+    }
+
+    @Override
+    public boolean customizesBaseLayout() {
+        return true;
+    }
+
+    @Override
+    public AppStyledViewControllerOEMV1 createAppStyledView(Context sourceContext) {
+        //return new AppStyleViewControllerImpl(mPluginContext);
+        return null;
+    }
+
+    @Override
+    public RecyclerViewOEMV1 createRecyclerView(
+            Context sourceContext,
+            RecyclerViewAttributesOEMV1 attrs) {
+        return new RecyclerViewImpl(mPluginContext, attrs);
+    }
+
+    @Override
+    public AdapterOEMV1<? extends ViewHolderOEMV1> createListItemAdapter(
+            List<ListItemOEMV1> items) {
+        //return new ListItemAdapter(mPluginContext, items);
+        return null;
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/SecureView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/SecureView.java
new file mode 100644
index 0000000..97b0c4d
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/SecureView.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A view that cannot be clicked on when the window is obscured.
+ */
+public class SecureView extends View {
+
+    private boolean mSecure = true;
+
+    public SecureView(Context context) {
+        super(context);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public SecureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+        if (!mSecure) {
+            return super.onFilterTouchEventForSecurity(event);
+        }
+
+        int flags = MotionEvent.FLAG_WINDOW_IS_OBSCURED;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            flags |= MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+        }
+
+        return (event.getFlags() & flags) == 0 && super.onFilterTouchEventForSecurity(event);
+    }
+
+    /** Sets if this view should ignore touch events when the window is obscured. Default true. */
+    public void setSecure(boolean secure) {
+        mSecure = secure;
+    }
+
+    /** Returns if this view should ignore touch events when the window is obscured. */
+    public boolean isSecure() {
+        return mSecure;
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/Utils.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/Utils.java
new file mode 100644
index 0000000..1d28376
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/Utils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+import androidx.annotation.DimenRes;
+
+/**
+ * Collection of utility methods
+ */
+public final class Utils {
+
+    /** This is a utility class */
+    private Utils() {
+    }
+
+    /**
+     * Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
+     * is not currently public.
+     *
+     * @param res   {@link Resources} to read values from
+     * @param resId Id of the dimens resource to read
+     */
+    public static float getFloat(Resources res, @DimenRes int resId) {
+        TypedValue outValue = new TypedValue();
+        res.getValue(resId, outValue, true);
+        return outValue.getFloat();
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/appstyleview/AppStyleViewControllerImpl.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/appstyleview/AppStyleViewControllerImpl.java
new file mode 100644
index 0000000..3d27977
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/appstyleview/AppStyleViewControllerImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.appstyleview;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.ImageView;
+import android.widget.ScrollView;
+
+import com.android.car.ui.plugin.oemapis.appstyledview.AppStyledViewControllerOEMV1;
+
+import com.chassis.car.ui.plugin.R;
+
+/**
+ * The OEM implementation for {@link AppStyledViewControllerOEMV1} for a AppStyledView.
+ */
+public class AppStyleViewControllerImpl implements AppStyledViewControllerOEMV1 {
+
+    private final Context mPluginContext;
+    private final View mAppStyleView;
+
+    public AppStyleViewControllerImpl(Context pluginContext) {
+        mPluginContext = pluginContext;
+        LayoutInflater inflater = LayoutInflater.from(mPluginContext);
+        mAppStyleView = inflater.inflate(R.layout.app_styled_view, null, false);
+    }
+
+    @Override
+    public View getView() {
+        return mAppStyleView;
+    }
+
+    @Override
+    public void setContent(View content) {
+        ScrollView scrollview = mAppStyleView.requireViewById(R.id.app_styled_content);
+        scrollview.removeAllViews();
+        scrollview.addView(content);
+    }
+
+    @Override
+    public void setOnBackClickListener(Runnable listener) {
+        ImageView navIconView = mAppStyleView.requireViewById(R.id.app_styled_view_icon_close);
+        if (listener == null) {
+            navIconView.setOnClickListener(null);
+            navIconView.setClickable(false);
+        } else {
+            navIconView.setOnClickListener(v -> listener.run());
+        }
+    }
+
+    @Override
+    public void setNavIcon(int navIcon) {
+        ImageView navIconView = mAppStyleView.requireViewById(R.id.app_styled_view_icon_close);
+        navIconView.setVisibility(navIcon == AppStyledViewControllerOEMV1.NAV_ICON_DISABLED
+                ? View.INVISIBLE : View.VISIBLE);
+        if (navIcon == AppStyledViewControllerOEMV1.NAV_ICON_BACK) {
+            navIconView.setImageResource(R.drawable.icon_back);
+        } else if (navIcon == AppStyledViewControllerOEMV1.NAV_ICON_CLOSE) {
+            navIconView.setImageResource(R.drawable.icon_close);
+        }
+    }
+
+    @Override
+    public LayoutParams getDialogWindowLayoutParam(LayoutParams params) {
+        params.width = Math.round(
+                mPluginContext.getResources().getDimension(R.dimen.app_styled_dialog_width));
+        params.height = Math.round(
+                mPluginContext.getResources().getDimension(R.dimen.app_styled_dialog_height));
+        params.gravity = Gravity.TOP | Gravity.START;
+        params.x = Math.round(
+                mPluginContext.getResources().getDimension(R.dimen.app_styled_dialog_position_x));
+        params.y = Math.round(
+                mPluginContext.getResources().getDimension(R.dimen.app_styled_dialog_position_y));
+        return params;
+    }
+}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/AdapterWrapper.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/AdapterWrapper.java
similarity index 86%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/AdapterWrapper.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/AdapterWrapper.java
index fd7503a..a777cfb 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/AdapterWrapper.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/AdapterWrapper.java
@@ -13,20 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.recyclerview;
+package com.chassis.car.ui.plugin.recyclerview;
 
+import android.annotation.SuppressLint;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterDataObserverOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterDataObserverOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.recyclerview.AdapterWrapper.ViewHolderWrapper;
+import com.chassis.car.ui.plugin.recyclerview.AdapterWrapper.ViewHolderWrapper;
 
 /**
  * Wrapper class that passes the data to car-ui via AdapterOEMV1 interface
@@ -34,11 +34,12 @@
 public final class AdapterWrapper extends RecyclerView.Adapter<ViewHolderWrapper> {
 
     @NonNull
-    private AdapterOEMV1 mAdapter;
+    private final AdapterOEMV1 mAdapter;
 
     @NonNull
-    private AdapterDataObserverOEMV1 mAdapterDataObserver = new AdapterDataObserverOEMV1() {
+    private final AdapterDataObserverOEMV1 mAdapterDataObserver = new AdapterDataObserverOEMV1() {
         @Override
+        @SuppressLint("NotifyDataSetChanged")
         public void onChanged() {
             AdapterWrapper.super.notifyDataSetChanged();
         }
@@ -75,7 +76,7 @@
         }
     };
 
-    public AdapterWrapper(@NonNull AdapterOEMV1 adapter) {
+    public AdapterWrapper(@NonNull AdapterOEMV1<?> adapter) {
         this.mAdapter = adapter;
         AdapterWrapper.super.setHasStableIds(adapter.hasStableIds());
         updateStateRestorationPolicy();
@@ -115,7 +116,7 @@
 
     @Override
     public void onAttachedToRecyclerView(RecyclerView recyclerView) {
-        mAdapter.onAttachedToRecyclerView((RecyclerViewOEMV1) recyclerView);
+        mAdapter.onAttachedToRecyclerView(null);
     }
 
     @Override
@@ -130,7 +131,7 @@
 
     @Override
     public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
-        mAdapter.onDetachedFromRecyclerView((RecyclerViewOEMV1) recyclerView);
+        mAdapter.onDetachedFromRecyclerView(null);
     }
 
     @Override
@@ -187,4 +188,11 @@
             return mViewHolder;
         }
     }
+
+    /**
+     * returns the wrapped {@link AdapterOEMV1}
+     */
+    public AdapterOEMV1<?> getOEMAdapter() {
+        return mAdapter;
+    }
 }
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/DefaultScrollBar.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/DefaultScrollBar.java
new file mode 100644
index 0000000..aa7ef3e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/DefaultScrollBar.java
@@ -0,0 +1,645 @@
+/*
+ * 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.chassis.car.ui.plugin.recyclerview;
+
+import static com.chassis.car.ui.plugin.Utils.getFloat;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.OrientationHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.LayoutManager;
+
+import com.chassis.car.ui.plugin.R;
+
+/**
+ * <p>Inspired by {@link androidx.car.widget.PagedListView}. Most pagination and scrolling logic
+ * has been ported from the PLV with minor updates.
+ */
+/* package */ final class DefaultScrollBar {
+
+    private float mButtonDisabledAlpha;
+    private SnapHelper mSnapHelper;
+
+    private View mScrollView;
+    private View mScrollTrack;
+    private View mScrollThumb;
+    private View mUpButton;
+    private View mDownButton;
+    private int mScrollbarThumbMinHeight;
+
+    private Context mContext;
+    private RecyclerView mRecyclerView;
+
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private OrientationHelper mOrientationHelper;
+
+    private OnContinuousScrollListener mPageUpOnContinuousScrollListener;
+    private OnContinuousScrollListener mPageDownOnContinuousScrollListener;
+
+    public void initialize(Context context, RecyclerView rv, View scrollView) {
+        mContext = context;
+        mRecyclerView = rv;
+
+        mScrollView = scrollView;
+
+        Resources res = context.getResources();
+
+        mButtonDisabledAlpha = getFloat(res, R.dimen.button_disabled_alpha);
+        mScrollbarThumbMinHeight = (int) context.getResources()
+                .getDimension(R.dimen.scrollbar_min_thumb_height);
+
+        mUpButton = mScrollView.requireViewById(R.id.scrollbar_page_up);
+        View.OnClickListener paginateUpButtonOnClickListener = v -> pageUp();
+        mUpButton.setOnClickListener(paginateUpButtonOnClickListener);
+        mPageUpOnContinuousScrollListener = new OnContinuousScrollListener(context,
+                paginateUpButtonOnClickListener);
+        mUpButton.setOnTouchListener(mPageUpOnContinuousScrollListener);
+
+        mDownButton = mScrollView.requireViewById(R.id.scrollbar_page_down);
+        View.OnClickListener paginateDownButtonOnClickListener = v -> pageDown();
+        mDownButton.setOnClickListener(paginateDownButtonOnClickListener);
+        mPageDownOnContinuousScrollListener = new OnContinuousScrollListener(context,
+                paginateDownButtonOnClickListener);
+        mDownButton.setOnTouchListener(mPageDownOnContinuousScrollListener);
+
+        mScrollTrack = mScrollView.requireViewById(R.id.scrollbar_track);
+        mScrollThumb = mScrollView.requireViewById(R.id.scrollbar_thumb);
+
+        mSnapHelper = new SnapHelper(context);
+        getRecyclerView().setOnFlingListener(null);
+        mSnapHelper.attachToRecyclerView(getRecyclerView());
+
+        // enables fast scrolling.
+        FastScroller fastScroller = new FastScroller(mRecyclerView, mScrollTrack, mScrollView);
+        fastScroller.enable();
+
+        getRecyclerView().addOnScrollListener(mRecyclerViewOnScrollListener);
+
+        mScrollView.setVisibility(View.GONE);
+        mScrollView.addOnLayoutChangeListener(
+                (View v,
+                        int left,
+                        int top,
+                        int right,
+                        int bottom,
+                        int oldLeft,
+                        int oldTop,
+                        int oldRight,
+                        int oldBottom) -> mHandler.post(this::updatePaginationButtons));
+
+        if (mRecyclerView.getAdapter() != null) {
+            adapterChanged(mRecyclerView.getAdapter());
+        }
+    }
+
+    public RecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    public void requestLayout() {
+        mScrollView.requestLayout();
+    }
+
+    public void setPadding(int paddingStart, int paddingEnd) {
+        mScrollView.setPadding(mScrollView.getPaddingLeft(), paddingStart,
+                mScrollView.getPaddingRight(), paddingEnd);
+    }
+
+    public void adapterChanged(@Nullable RecyclerView.Adapter<?> adapter) {
+        try {
+            if (mRecyclerView.getAdapter() != null) {
+                mRecyclerView.getAdapter().unregisterAdapterDataObserver(mAdapterChangeObserver);
+            }
+        } catch (IllegalStateException e) {
+            // adapter was not registered and we're trying to unregister again. ignore.
+        }
+
+        try {
+            if (adapter != null) {
+                adapter.registerAdapterDataObserver(mAdapterChangeObserver);
+            }
+        } catch (IllegalStateException e) {
+            // adapter is already registered. and we're trying to register again. ignore.
+        }
+    }
+
+    /**
+     * 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) {
+        // If the button is held down the button is disabled, the MotionEvent.ACTION_UP event on
+        // button release will not be sent to cancel pending scrolls. Manually cancel any pending
+        // scroll.
+        if (!enabled) {
+            mPageUpOnContinuousScrollListener.cancelPendingScroll();
+        }
+
+        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) {
+        // If the button is held down the button is disabled, the MotionEvent.ACTION_UP event on
+        // button release will not be sent to cancel pending scrolls. Manually cancel any pending
+        // scroll.
+        if (!enabled) {
+            mPageDownOnContinuousScrollListener.cancelPendingScroll();
+        }
+
+        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();
+    }
+
+    /**
+     * 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
+     */
+    private void setParameters(
+            @IntRange(from = 0) int range,
+            @IntRange(from = 0) int offset,
+            @IntRange(from = 0) int extent) {
+        // 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.VISIBLE || 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 || thumbLength < mScrollThumb.getHeight()) {
+            lp.height = thumbLength;
+            mScrollThumb.requestLayout();
+        }
+
+        moveY(mScrollThumb, thumbOffset);
+    }
+
+    /**
+     * 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 max(Math.round(((float) extent / range) * mScrollTrack.getHeight()),
+                min(mScrollbarThumbMinHeight, mScrollTrack.getHeight()));
+    }
+
+    /**
+     * 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 scrollbar track is.
+        return mScrollTrack.getTop()
+                + (isDownEnabled()
+                ? Math.round(((float) offset / range) * (mScrollTrack.getHeight() - thumbLength))
+                : mScrollTrack.getHeight() - thumbLength);
+    }
+
+    /**
+     * Moves the given view to the specified 'y' position.
+     */
+    private void moveY(final View view, float newPosition) {
+        view.animate()
+                .y(newPosition)
+                .setDuration(/* duration= */ 0)
+                .setInterpolator(mPaginationInterpolator)
+                .start();
+    }
+
+    private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                    updatePaginationButtons();
+                    cacheChildrenHeight(recyclerView.getLayoutManager());
+                }
+            };
+    private final SparseIntArray mChildHeightByAdapterPosition = new SparseIntArray();
+
+    private final RecyclerView.AdapterDataObserver mAdapterChangeObserver =
+            new RecyclerView.AdapterDataObserver() {
+                @Override
+                public void onChanged() {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeInserted(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                    clearCachedHeights();
+                }
+                @Override
+                public void onItemRangeRemoved(int positionStart, int itemCount) {
+                    clearCachedHeights();
+                }
+            };
+
+    private void clearCachedHeights() {
+        mChildHeightByAdapterPosition.clear();
+        cacheChildrenHeight(getLayoutManager());
+    }
+
+    private void cacheChildrenHeight(@Nullable LayoutManager layoutManager) {
+        if (layoutManager == null) {
+            return;
+        }
+        for (int i = 0; i < layoutManager.getChildCount(); i++) {
+            View child = layoutManager.getChildAt(i);
+            if (child != null) {
+                int childPosition = layoutManager.getPosition(child);
+                if (mChildHeightByAdapterPosition.indexOfKey(childPosition) < 0) {
+                    mChildHeightByAdapterPosition.put(childPosition, child.getHeight());
+                }
+            }
+        }
+    }
+
+    private int estimateNextPositionScrollUp(int currentPos, int scrollDistance,
+            OrientationHelper orientationHelper) {
+        int nextPos = 0;
+        int distance = 0;
+        for (int i = currentPos - 1; i >= 0; i--) {
+            if (mChildHeightByAdapterPosition.indexOfKey(i) < 0) {
+                // Use the average height estimate when there is not enough data
+                nextPos = mSnapHelper.estimateNextPositionDiffForScrollDistance(
+                        orientationHelper, -scrollDistance);
+                break;
+            }
+            if ((distance + mChildHeightByAdapterPosition.get(i)) > Math.abs(scrollDistance)) {
+                nextPos = i - currentPos + 1;
+                break;
+            }
+            distance += mChildHeightByAdapterPosition.get(i);
+        }
+        return nextPos;
+    }
+
+    private OrientationHelper getOrientationHelper(LayoutManager layoutManager) {
+        if (mOrientationHelper == null || mOrientationHelper.getLayoutManager() != layoutManager) {
+            // RecyclerViewImpl 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 RecyclerViewImpl}.
+     *
+     * <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
+     * RecyclerViewImpl}, then the snapping will not occur.
+     */
+    void pageUp() {
+        int currentOffset = computeVerticalScrollOffset();
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0 || currentOffset == 0) {
+            return;
+        }
+
+        // Use OrientationHelper to calculate scroll distance in order to match snapping behavior.
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+        int scrollDistance = orientationHelper.getTotalSpace();
+
+        View currentPosView = getFirstMostVisibleChild(orientationHelper);
+        int currentPos = currentPosView != null ? getLayoutManager().getPosition(
+                currentPosView) : 0;
+        int nextPos = estimateNextPositionScrollUp(currentPos,
+                scrollDistance - Math.max(0, orientationHelper.getStartAfterPadding()
+                        - orientationHelper.getDecoratedStart(currentPosView)), orientationHelper);
+        if (nextPos == 0) {
+            // Distance should always be positive. Negate its value to scroll up.
+            smoothScrollBy(0, -scrollDistance);
+        } else {
+            smoothScrollToPosition(Math.max(0, currentPos + nextPos));
+        }
+    }
+
+    private View getFirstMostVisibleChild(OrientationHelper helper) {
+        float mostVisiblePercent = 0;
+        View mostVisibleView = null;
+
+        for (int i = 0; i < getLayoutManager().getChildCount(); i++) {
+            View child = getLayoutManager().getChildAt(i);
+            float visiblePercentage = SnapHelper.getPercentageVisible(child, helper);
+            if (visiblePercentage == 1f) {
+                mostVisibleView = child;
+                break;
+            } else if (visiblePercentage > mostVisiblePercent) {
+                mostVisiblePercent = visiblePercentage;
+                mostVisibleView = child;
+            }
+        }
+
+        return mostVisibleView;
+    }
+
+    /**
+     * Scrolls the contents of the RecyclerView down a page. A page is defined as the height of the
+     * {@code RecyclerViewImpl}.
+     *
+     * <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 RecyclerViewImpl}, then it will be
+     * scrolled the length of a page, but not snapped to.
+     */
+    void pageDown() {
+        LayoutManager layoutManager = getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return;
+        }
+
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+        int screenSize = orientationHelper.getTotalSpace();
+        int scrollDistance = screenSize;
+
+        View currentPosView = getFirstMostVisibleChild(orientationHelper);
+
+        // If current view is partially visible and bottom of the view is below visible area of
+        // the recyclerview either scroll down one page (screenSize) or enough to align the bottom
+        // of the view with the bottom of the recyclerview. Note that this will not cause a snap,
+        // because the current view is already snapped to the top or it wouldn't be the most
+        // visible view.
+        if (layoutManager.isViewPartiallyVisible(currentPosView,
+                /* completelyVisible= */ false, /* acceptEndPointInclusion= */ false)
+                        && orientationHelper.getDecoratedEnd(currentPosView)
+                                > orientationHelper.getEndAfterPadding()) {
+            scrollDistance = Math.min(screenSize,
+                    orientationHelper.getDecoratedEnd(currentPosView)
+                            - orientationHelper.getEndAfterPadding());
+        }
+
+        // Iterate over the childview (bottom to top) and stop when we find the first
+        // view that we can snap to and the scroll size is less than max scroll size (screenSize)
+        for (int i = layoutManager.getChildCount() - 1; i >= 0; i--) {
+            View child = layoutManager.getChildAt(i);
+
+            // Ignore the child if it's above the currentview, as scrolldown will only move down.
+            // Note that in case of gridview, child will not be the same as the currentview.
+            if (orientationHelper.getDecoratedStart(child)
+                    <= orientationHelper.getDecoratedStart(currentPosView)) {
+                break;
+            }
+
+            // Ignore the child if the scroll distance is bigger than the max scroll size
+            if (orientationHelper.getDecoratedStart(child)
+                    - orientationHelper.getStartAfterPadding() <= screenSize) {
+                // If the child is already fully visible we can scroll even further.
+                if (orientationHelper.getDecoratedEnd(child)
+                        <= orientationHelper.getEndAfterPadding()) {
+                    scrollDistance = orientationHelper.getDecoratedEnd(child)
+                            - orientationHelper.getStartAfterPadding();
+                } else {
+                    scrollDistance = orientationHelper.getDecoratedStart(child)
+                            - orientationHelper.getStartAfterPadding();
+                }
+                break;
+            }
+        }
+
+        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.
+     */
+    private void updatePaginationButtons() {
+        LayoutManager layoutManager = getLayoutManager();
+
+        if (layoutManager == null) {
+            mScrollView.setVisibility(View.GONE);
+            return;
+        }
+
+        boolean isAtStart = isAtStart();
+        boolean isAtEnd = isAtEnd();
+
+        // enable/disable the button before the view is shown. So there is no flicker.
+        setUpEnabled(!isAtStart);
+        setDownEnabled(!isAtEnd);
+
+        boolean isScrollViewVisiblePreUpdate = mScrollView.getVisibility() == View.VISIBLE;
+        boolean isLayoutRequired = false;
+
+        if ((isAtStart && isAtEnd) || layoutManager.getItemCount() == 0) {
+            mScrollView.setVisibility(View.GONE);
+        } else {
+            OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+            int screenSize = orientationHelper.getTotalSpace();
+            int touchTargetSize = (int) mContext.getResources()
+                    .getDimension(R.dimen.scrollbar_button_size);
+            ViewGroup.MarginLayoutParams upButtonLayoutParam =
+                    (ViewGroup.MarginLayoutParams) mUpButton.getLayoutParams();
+            int upButtonMargin = upButtonLayoutParam.topMargin
+                    + upButtonLayoutParam.bottomMargin;
+            ViewGroup.MarginLayoutParams downButtonLayoutParam =
+                    (ViewGroup.MarginLayoutParams) mDownButton.getLayoutParams();
+            int downButtonMargin = downButtonLayoutParam.topMargin
+                    + downButtonLayoutParam.bottomMargin;
+            int margin = upButtonMargin + downButtonMargin;
+            if (screenSize < 2 * touchTargetSize + margin) {
+                if (isScrollViewVisiblePreUpdate) {
+                    isLayoutRequired = true;
+                }
+                mScrollView.setVisibility(View.GONE);
+            } else {
+                ViewGroup.MarginLayoutParams trackLayoutParam =
+                        (ViewGroup.MarginLayoutParams) mScrollTrack.getLayoutParams();
+                int trackMargin = trackLayoutParam.topMargin
+                        + trackLayoutParam.bottomMargin;
+                margin += trackMargin;
+                // touchTargetSize (for up button) + touchTargetSize (for down button)
+                // + max(touchTargetSize, mScrollbarThumbMinHeight)
+                // + margin (all margins added together)
+                if (screenSize < 2 * touchTargetSize
+                        + max(touchTargetSize, mScrollbarThumbMinHeight) + margin) {
+                    mScrollTrack.setVisibility(View.INVISIBLE);
+                    mScrollThumb.setVisibility(View.INVISIBLE);
+                } else {
+                    mScrollTrack.setVisibility(View.VISIBLE);
+                    mScrollThumb.setVisibility(View.VISIBLE);
+                }
+
+                if (!isScrollViewVisiblePreUpdate) {
+                    isLayoutRequired = true;
+                }
+                mScrollView.setVisibility(View.VISIBLE);
+            }
+        }
+
+        if (layoutManager.canScrollVertically()) {
+            setParameters(
+                    computeVerticalScrollRange(),
+                    computeVerticalScrollOffset(),
+                    computeVerticalScrollExtent());
+        } else {
+            setParameters(
+                    computeHorizontalScrollRange(),
+                    computeHorizontalScrollOffset(),
+                    computeHorizontalScrollExtent());
+        }
+
+        mScrollView.invalidate();
+        // updatePaginationButtons() is called from onLayoutChangeListener, request layout only when
+        // required to avoid infinite loop.
+        if (isLayoutRequired) {
+            // If currently performing a layout pass, layout update may not be picked up until the
+            // next layout pass. Schedule another layout pass to ensure changes take affect.
+            mScrollView.post(() -> mScrollView.requestLayout());
+        }
+    }
+
+    /**
+     * Returns {@code true} if the RecyclerView is completely displaying the first item.
+     */
+    public boolean isAtStart() {
+        return mSnapHelper.isAtStart(getLayoutManager());
+    }
+
+    public void setHighlightThumb(boolean highlight) {
+        mScrollThumb.setActivated(highlight);
+    }
+
+    /**
+     * Returns {@code true} if the RecyclerView is completely displaying the last item.
+     */
+    boolean isAtEnd() {
+        return mSnapHelper.isAtEnd(getLayoutManager());
+    }
+
+    @VisibleForTesting
+    LayoutManager getLayoutManager() {
+        return getRecyclerView().getLayoutManager();
+    }
+
+    @VisibleForTesting
+    void smoothScrollToPosition(int max) {
+        getRecyclerView().smoothScrollToPosition(max);
+    }
+
+    @VisibleForTesting
+    void smoothScrollBy(int dx, int dy) {
+        getRecyclerView().smoothScrollBy(dx, dy);
+    }
+
+    @VisibleForTesting
+    int computeVerticalScrollRange() {
+        return getRecyclerView().computeVerticalScrollRange();
+    }
+
+    @VisibleForTesting
+    int computeVerticalScrollOffset() {
+        return getRecyclerView().computeVerticalScrollOffset();
+    }
+
+    @VisibleForTesting
+    int computeVerticalScrollExtent() {
+        return getRecyclerView().computeVerticalScrollExtent();
+    }
+
+    @VisibleForTesting
+    int computeHorizontalScrollRange() {
+        return getRecyclerView().computeHorizontalScrollRange();
+    }
+
+    @VisibleForTesting
+    int computeHorizontalScrollOffset() {
+        return getRecyclerView().computeHorizontalScrollOffset();
+    }
+
+    @VisibleForTesting
+    int computeHorizontalScrollExtent() {
+        return getRecyclerView().computeHorizontalScrollExtent();
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/FastScroller.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/FastScroller.java
new file mode 100644
index 0000000..9378a36
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/FastScroller.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.recyclerview;
+
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.chassis.car.ui.plugin.R;
+
+/**
+ * Class responsible for fast scrolling. This class offers two functionalities.
+ * <ul>
+ *     <li>User can hold the thumb and drag.</li>
+ *     <li>User can click anywhere on the track and thumb will scroll to that position.</li>
+ * </ul>
+ */
+/* package */ final class FastScroller implements View.OnTouchListener {
+
+    private float mTouchDownY = -1;
+
+    @NonNull
+    private final View mScrollTrackView;
+    @NonNull
+    private final View mScrollThumb;
+    @NonNull
+    private final RecyclerView mRecyclerView;
+    private final int mClickActionThreshold;
+
+    FastScroller(@NonNull RecyclerView recyclerView, @NonNull View scrollTrackView,
+                 @NonNull View scrollView) {
+        mRecyclerView = recyclerView;
+        mScrollTrackView = scrollTrackView;
+        mScrollThumb = scrollView.requireViewById(R.id.scrollbar_thumb);
+        mClickActionThreshold = ViewConfiguration.get(
+                recyclerView.getContext()).getScaledTouchSlop();
+    }
+
+    void enable() {
+        mScrollTrackView.setOnTouchListener(this);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent me) {
+        switch (me.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mTouchDownY = me.getY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float thumbBottom = mScrollThumb.getY() + mScrollThumb.getHeight();
+                // check if the move coordinates are within the bounds of the thumb. i.e. user is
+                // holding and dragging the thumb.
+                if (!(me.getY() + mScrollTrackView.getY() < thumbBottom
+                        && me.getY() + mScrollTrackView.getY() > mScrollThumb.getY())) {
+                    // don't do anything if touch is detected outside the thumb
+                    return true;
+                }
+                // calculate where the center of the thumb is on the screen.
+                float thumbCenter = mScrollThumb.getY() + mScrollThumb.getHeight() / 2.0f;
+                // me.getY() returns the coordinates relative to the view. For example, if we
+                // click the top left of the scroll track the coordinates will be 0,0. Hence, we
+                // need to add the relative coordinates to the actual coordinates computed by the
+                // thumb center and add them to get the final Y coordinate. "(me.getY() -
+                // mTouchDownY)" calculates the distance that is moved from the previous touch
+                // event.
+                verticalScrollTo(thumbCenter + (me.getY() - mTouchDownY));
+                mTouchDownY = me.getY();
+                break;
+            case MotionEvent.ACTION_UP:
+            default:
+                if (isClick(mTouchDownY, me.getY())) {
+                    verticalScrollTo(me.getY() + mScrollTrackView.getY());
+                }
+                mTouchDownY = -1;
+        }
+        return true;
+    }
+
+    /**
+     * Checks if the start and end points are within the threshold to be considered as a click.
+     */
+    private boolean isClick(float startY, float endY) {
+        return Math.abs(startY - endY) < mClickActionThreshold;
+    }
+
+    private void verticalScrollTo(float y) {
+        int scrollingBy = calculateScrollDistance(y);
+        if (scrollingBy != 0) {
+            mRecyclerView.scrollBy(0, scrollingBy);
+        }
+    }
+
+    private int calculateScrollDistance(float newDragPos) {
+        final int[] scrollbarRange = getVerticalRange();
+        int scrollbarLength = scrollbarRange[1] - scrollbarRange[0];
+
+        float thumbCenter = mScrollThumb.getY() + mScrollThumb.getHeight() / 2.0f;
+
+        if (scrollbarLength == 0) {
+            return 0;
+        }
+        // percentage of data to be scrolled.
+        float percentage = ((newDragPos - thumbCenter) / (float) scrollbarLength);
+        int totalPossibleOffset =
+                mRecyclerView.computeVerticalScrollRange() - mRecyclerView.getHeight();
+        return (int) (percentage * totalPossibleOffset);
+    }
+
+    /**
+     * Gets the (min, max) vertical positions of the vertical scroll bar. The range starts from the
+     * center of thumb when thumb is top aligned to center of the thumb when thumb is bottom
+     * aligned.
+     */
+    private int[] getVerticalRange() {
+        int[] verticalRange = new int[2];
+        verticalRange[0] = (int) mScrollTrackView.getY() + mScrollThumb.getHeight() / 2;
+        verticalRange[1] = (int) mScrollTrackView.getY() + mScrollTrackView.getHeight()
+                - mScrollThumb.getHeight() / 2;
+        return verticalRange;
+    }
+}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/ListItemAdapter.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/ListItemAdapter.java
similarity index 91%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/ListItemAdapter.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/ListItemAdapter.java
index 863f518..6d24096 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/ListItemAdapter.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/ListItemAdapter.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.recyclerview;
+package com.chassis.car.ui.plugin.recyclerview;
 
+import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -31,15 +32,17 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterDataObserverOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ContentListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.HeaderListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
+import com.android.car.ui.plugin.oemapis.TextOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterDataObserverOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ContentListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.HeaderListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ListItemOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
+import com.chassis.car.ui.plugin.R;
+import com.chassis.car.ui.plugin.SecureView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -54,6 +57,7 @@
     static final int VIEW_TYPE_LIST_ITEM = 1;
     static final int VIEW_TYPE_LIST_HEADER = 2;
 
+    private final Context mContext;
     private final List<? extends ListItemOEMV1> mItems;
     @NonNull
     private final List<AdapterDataObserverOEMV1> mAdapterDataObservers = new ArrayList<>();
@@ -112,7 +116,8 @@
                 }
             };
 
-    public ListItemAdapter(List<? extends ListItemOEMV1> items) {
+    public ListItemAdapter(Context context, List<? extends ListItemOEMV1> items) {
+        mContext = context;
         mItems = items;
     }
 
@@ -120,7 +125,7 @@
     @Override
     public ListItemAdapter.BaseViewHolder onCreateViewHolder(
             @NonNull ViewGroup parent, int viewType) {
-        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        LayoutInflater inflater = LayoutInflater.from(mContext);
 
         switch (viewType) {
             case VIEW_TYPE_LIST_ITEM:
@@ -169,16 +174,6 @@
     }
 
     @Override
-    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
-        registerAdapterDataObserver(mAdapterDataObserver);
-    }
-
-    @Override
-    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
-        unregisterAdapterDataObserver(mAdapterDataObserver);
-    }
-
-    @Override
     public void registerAdapterDataObserver(AdapterDataObserverOEMV1 observer) {
         if (observer == null) {
             return;
@@ -195,8 +190,8 @@
     }
 
     @Override
-    public void setRecyclerView(View recyclerview) {
-        //  Do nothing. This method will never be invoked.
+    public void setMaxItems(int maxItems) {
+        // TODO(b/202433866):
     }
 
     @Override
@@ -211,7 +206,7 @@
                 if (!(item instanceof ContentListItemOEMV1)) {
                     throw new IllegalStateException(
                             "Expected item to be bound to viewHolder to be instance of "
-                                    + "CarUiContentListItem.");
+                                    + "ContentListItemOEMV1.");
                 }
 
                 ((ListItemViewHolder) holder).bind((ContentListItemOEMV1) item);
@@ -225,7 +220,7 @@
                 if (!(header instanceof HeaderListItemOEMV1)) {
                     throw new IllegalStateException(
                             "Expected item to be bound to viewHolder to be instance of "
-                                    + "CarUiHeaderListItem.");
+                                    + "HeaderListItemOEMV1.");
                 }
 
                 ((HeaderViewHolder) holder).bind((HeaderListItemOEMV1) header);
@@ -263,9 +258,9 @@
         final CheckBox mCheckBox;
         final RadioButton mRadioButton;
         final ImageView mSupplementalIcon;
-        final View mTouchInterceptor;
-        final View mReducedTouchInterceptor;
-        final View mActionContainerTouchInterceptor;
+        final SecureView mTouchInterceptor;
+        final SecureView mReducedTouchInterceptor;
+        final SecureView mActionContainerTouchInterceptor;
 
         ListItemViewHolder(@NonNull View itemView) {
             super(itemView);
@@ -291,19 +286,23 @@
 
         void bind(@NonNull ContentListItemOEMV1 item) {
             if (item.getTitle() != null) {
-                mTitle.setText(item.getTitle());
+                mTitle.setText(item.getTitle().getPreferredText());
                 mTitle.setVisibility(View.VISIBLE);
             } else {
                 mTitle.setVisibility(View.GONE);
             }
 
             if (item.getBody() != null) {
-                mBody.setText(TextUtils.join("\n", item.getBody()));
+                mBody.setText(TextOEMV1.combineMultiLine(item.getBody()));
                 mBody.setVisibility(View.VISIBLE);
             } else {
                 mBody.setVisibility(View.GONE);
             }
 
+            mTouchInterceptor.setSecure(item.isSecure());
+            mReducedTouchInterceptor.setSecure(item.isSecure());
+            mActionContainerTouchInterceptor.setSecure(item.isSecure());
+
             mIcon.setVisibility(View.GONE);
             mContentIcon.setVisibility(View.GONE);
             mAvatarIcon.setVisibility(View.GONE);
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/OnContinuousScrollListener.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/OnContinuousScrollListener.java
new file mode 100644
index 0000000..00e32c4
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/OnContinuousScrollListener.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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.chassis.car.ui.plugin.recyclerview;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+
+import androidx.annotation.NonNull;
+
+import com.chassis.car.ui.plugin.R;
+
+/**
+ * A class, that can be used as a TouchListener on any view (e.g. a Button). It periodically calls
+ * the provided clickListener. The first callback is fired after the initial Delay, and subsequent
+ * ones after the defined interval.
+ */
+public final class OnContinuousScrollListener implements OnTouchListener {
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final int mInitialDelay;
+    private final int mRepeatInterval;
+    private final OnClickListener mOnClickListener;
+    private View mTouchedView;
+    private boolean mIsLongPressed;
+
+    /**
+     * Notifies listener and self schedules to be re-run at next callback interval.
+     */
+    private final Runnable mPeriodicRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mTouchedView.isEnabled()) {
+                mHandler.postDelayed(this, mRepeatInterval);
+                mOnClickListener.onClick(mTouchedView);
+                mIsLongPressed = true;
+            } else {
+                mIsLongPressed = false;
+            }
+        }
+    };
+
+    /**
+     * @param clickListener The OnClickListener, that will be called periodically
+     */
+    public OnContinuousScrollListener(@NonNull Context context,
+                                      @NonNull OnClickListener clickListener) {
+        this.mInitialDelay = context.getResources().getInteger(
+                R.integer.scrollbar_longpress_initial_delay);
+        this.mRepeatInterval = context.getResources().getInteger(
+                R.integer.scrollbar_longpress_repeat_interval);
+
+        if (mInitialDelay < 0 || mRepeatInterval < 0) {
+            throw new IllegalArgumentException("negative intervals are not allowed");
+        }
+        this.mOnClickListener = clickListener;
+    }
+
+    /**
+     * Cancel pending scroll operations. Any scroll operations that were scheduled to possibly be
+     * performed, as part of a continuous scroll, will be cancelled.
+     */
+    public void cancelPendingScroll() {
+        mHandler.removeCallbacks(mPeriodicRunnable);
+        mIsLongPressed = false;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        mTouchedView = view;
+        switch (motionEvent.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mHandler.removeCallbacks(mPeriodicRunnable);
+                mHandler.postDelayed(mPeriodicRunnable, mInitialDelay);
+                mTouchedView.setPressed(true);
+                return true;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (!mIsLongPressed) {
+                    mOnClickListener.onClick(view);
+                }
+                mHandler.removeCallbacks(mPeriodicRunnable);
+                mTouchedView.setPressed(false);
+                mIsLongPressed = false;
+                return true;
+        }
+        return false;
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/RecyclerViewImpl.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/RecyclerViewImpl.java
new file mode 100644
index 0000000..8551728
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/RecyclerViewImpl.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.recyclerview;
+
+import static androidx.recyclerview.widget.RecyclerView.VERTICAL;
+
+import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_LARGE;
+import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_MEDIUM;
+import static com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1.SIZE_SMALL;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.view.InputDevice;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.OrientationHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+import com.android.car.ui.plugin.oemapis.recyclerview.AdapterOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.LayoutStyleOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.OnChildAttachStateChangeListenerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.OnScrollListenerOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.RecyclerViewOEMV1;
+import com.android.car.ui.plugin.oemapis.recyclerview.ViewHolderOEMV1;
+
+import com.chassis.car.ui.plugin.R;
+import com.chassis.car.ui.plugin.recyclerview.AdapterWrapper.ViewHolderWrapper;
+import com.chassis.car.ui.plugin.uxr.CarUxRestrictionsUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reference OEM implementation for RecyclerView
+ */
+public final class RecyclerViewImpl extends FrameLayout implements RecyclerViewOEMV1 {
+
+    /**
+     * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_CONTAINER}
+     */
+    private static final String ROTARY_CONTAINER =
+            "com.android.car.ui.utils.ROTARY_CONTAINER";
+    /**
+     * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_HORIZONTALLY_SCROLLABLE}
+     */
+    private static final String ROTARY_HORIZONTALLY_SCROLLABLE =
+            "com.android.car.ui.utils.HORIZONTALLY_SCROLLABLE";
+    /**
+     * {@link com.android.car.ui.utils.RotaryConstants#ROTARY_VERTICALLY_SCROLLABLE}
+     */
+    private static final String ROTARY_VERTICALLY_SCROLLABLE =
+            "com.android.car.ui.utils.VERTICALLY_SCROLLABLE";
+
+    @NonNull
+    private final RecyclerView mRecyclerView;
+
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new UxRestrictionChangedListener();
+    @NonNull
+    private final CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+
+    @Nullable
+    private final DefaultScrollBar mScrollBar;
+
+    @NonNull
+    private final List<OnScrollListenerOEMV1> mScrollListeners = new ArrayList<>();
+
+    @NonNull
+    private final RecyclerView.OnScrollListener mOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                    for (OnScrollListenerOEMV1 listener : mScrollListeners) {
+                        listener.onScrolled(RecyclerViewImpl.this, dx, dy);
+                    }
+                }
+
+                @Override
+                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+                    for (OnScrollListenerOEMV1 listener : mScrollListeners) {
+                        listener.onScrollStateChanged(RecyclerViewImpl.this,
+                                toInternalScrollState(newState));
+                    }
+                }
+            };
+
+    @NonNull
+    private final List<OnChildAttachStateChangeListenerOEMV1> mOnChildAttachStateChangeListeners =
+            new ArrayList<>();
+
+    @NonNull
+    private final RecyclerView.OnChildAttachStateChangeListener mOnChildAttachStateChangeListener =
+            new OnChildAttachStateChangeListener() {
+                @Override
+                public void onChildViewAttachedToWindow(@NonNull View view) {
+                    for (OnChildAttachStateChangeListenerOEMV1 listener :
+                            mOnChildAttachStateChangeListeners) {
+                        listener.onChildViewAttachedToWindow(view);
+                    }
+                }
+
+                @Override
+                public void onChildViewDetachedFromWindow(@NonNull View view) {
+                    for (OnChildAttachStateChangeListenerOEMV1 listener :
+                            mOnChildAttachStateChangeListeners) {
+                        listener.onChildViewDetachedFromWindow(view);
+                    }
+                }
+            };
+
+    @Nullable
+    private LayoutStyleOEMV1 mLayoutStyle;
+
+    public RecyclerViewImpl(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public RecyclerViewImpl(@NonNull Context context,
+            @Nullable RecyclerViewAttributesOEMV1 attrs) {
+        super(context);
+        boolean scrollBarEnabled = context.getResources().getBoolean(R.bool.scrollbar_enable);
+        @LayoutRes int layout = R.layout.recycler_view_no_scrollbar;
+        if (scrollBarEnabled) {
+            int size = attrs != null ? attrs.getSize() : SIZE_LARGE;
+            switch (size) {
+                case SIZE_SMALL:
+                    layout = R.layout.recycler_view_small;
+                    break;
+                case SIZE_MEDIUM:
+                    layout = R.layout.recycler_view_medium;
+                    break;
+                case SIZE_LARGE:
+                    layout = R.layout.recycler_view;
+            }
+        }
+
+        LayoutInflater factory = LayoutInflater.from(context);
+        View rootView = factory.inflate(layout, this, true);
+        mRecyclerView = rootView.requireViewById(R.id.recycler_view);
+
+        // Set to false so the items below the toolbar are visible.
+        mRecyclerView.setClipToPadding(false);
+
+        mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+
+        if (attrs != null) {
+            setLayoutStyle(attrs.getLayoutStyle());
+            setBackground(attrs.getBackground());
+            setPadding(attrs.getPaddingLeft(), attrs.getPaddingTop(), attrs.getPaddingRight(),
+                    attrs.getPaddingBottom());
+            setMinimumHeight(attrs.getMinHeight());
+            setMinimumWidth(attrs.geMinWidth());
+
+            LayoutParams params = new LayoutParams(attrs.getLayoutWidth(), attrs.getLayoutHeight());
+            params.setMargins(attrs.getMarginLeft(), attrs.getMarginTop(), attrs.getMarginRight(),
+                    attrs.getMarginBottom());
+            setLayoutParams(params);
+
+            mLayoutStyle = attrs.getLayoutStyle();
+        } else {
+            mLayoutStyle = new LayoutStyleOEMV1() {
+                @Override
+                public int getSpanCount() {
+                    return 1;
+                }
+
+                @Override
+                public int getLayoutType() {
+                    return LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR;
+                }
+
+                @Override
+                public int getOrientation() {
+                    return LayoutStyleOEMV1.ORIENTATION_VERTICAL;
+                }
+
+                @Override
+                public boolean getReverseLayout() {
+                    return false;
+                }
+            };
+        }
+
+        setLayoutStyle(mLayoutStyle);
+
+        boolean rotaryScrollEnabled = attrs != null && attrs.isRotaryScrollEnabled();
+        initRotaryScroll(mRecyclerView, rotaryScrollEnabled, getLayoutStyle().getOrientation());
+
+        if (!scrollBarEnabled) {
+            mScrollBar = null;
+            return;
+        }
+
+        mRecyclerView.setVerticalScrollBarEnabled(false);
+        mRecyclerView.setHorizontalScrollBarEnabled(false);
+
+        mScrollBar = new DefaultScrollBar();
+        mScrollBar.initialize(context, mRecyclerView, rootView.requireViewById(R.id.scroll_bar));
+    }
+
+    @Override
+    public <V extends ViewHolderOEMV1> void setAdapter(AdapterOEMV1<V> adapterV1) {
+        if (adapterV1 == null) {
+            mRecyclerView.setAdapter(null);
+        } else {
+            mRecyclerView.setAdapter(new AdapterWrapper(adapterV1));
+        }
+    }
+
+    @Override
+    public void addOnScrollListener(OnScrollListenerOEMV1 listener) {
+        if (listener == null) {
+            return;
+        }
+        if (mScrollListeners.isEmpty()) {
+            mRecyclerView.addOnScrollListener(mOnScrollListener);
+        }
+        mScrollListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnScrollListener(OnScrollListenerOEMV1 listener) {
+        if (listener == null) {
+            return;
+        }
+        mScrollListeners.remove(listener);
+        if (mScrollListeners.isEmpty()) {
+            mRecyclerView.removeOnScrollListener(mOnScrollListener);
+        }
+    }
+
+    @Override
+    public void clearOnScrollListeners() {
+        if (!mScrollListeners.isEmpty()) {
+            mScrollListeners.clear();
+            mRecyclerView.clearOnScrollListeners();
+        }
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mRecyclerView.scrollToPosition(position);
+    }
+
+    @Override
+    public void smoothScrollBy(int dx, int dy) {
+        mRecyclerView.smoothScrollBy(dx, dy);
+    }
+
+    @Override
+    public void smoothScrollToPosition(int position) {
+        mRecyclerView.smoothScrollToPosition(position);
+    }
+
+    @Override
+    public void setHasFixedSize(boolean hasFixedSize) {
+        mRecyclerView.setHasFixedSize(hasFixedSize);
+    }
+
+    @Override
+    public boolean hasFixedSize() {
+        return mRecyclerView.hasFixedSize();
+    }
+
+    @Override
+    public void setLayoutStyle(@Nullable LayoutStyleOEMV1 layoutStyle) {
+        mLayoutStyle = layoutStyle;
+
+        int orientation = layoutStyle == null ? VERTICAL : layoutStyle.getOrientation();
+        boolean reverseLayout = layoutStyle != null && layoutStyle.getReverseLayout();
+
+        if (layoutStyle == null
+                || layoutStyle.getLayoutType() == LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR) {
+            mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
+                    orientation,
+                    reverseLayout));
+        } else {
+            GridLayoutManager glm = new GridLayoutManager(getContext(),
+                    layoutStyle.getSpanCount(),
+                    orientation,
+                    reverseLayout);
+            glm.setSpanSizeLookup(new SpanSizeLookup() {
+                @Override
+                public int getSpanSize(int position) {
+                    return layoutStyle.getSpanSize(position);
+                }
+            });
+            mRecyclerView.setLayoutManager(glm);
+        }
+    }
+
+    @Override
+    public LayoutStyleOEMV1 getLayoutStyle() {
+        return mLayoutStyle;
+    }
+
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public int findFirstCompletelyVisibleItemPosition() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            return ((LinearLayoutManager) layoutManager)
+                    .findFirstCompletelyVisibleItemPosition();
+        }
+        return 0;
+    }
+
+    @Override
+    public int findFirstVisibleItemPosition() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            return ((LinearLayoutManager) layoutManager)
+                    .findFirstVisibleItemPosition();
+        }
+        return 0;
+    }
+
+    @Override
+    public int findLastCompletelyVisibleItemPosition() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            return ((LinearLayoutManager) layoutManager)
+                    .findLastCompletelyVisibleItemPosition();
+        }
+        return 0;
+    }
+
+    @Override
+    public int findLastVisibleItemPosition() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager instanceof LinearLayoutManager) {
+            return ((LinearLayoutManager) layoutManager)
+                    .findLastVisibleItemPosition();
+        }
+        return 0;
+    }
+
+    private static int toInternalScrollState(int state) {
+        /* default to RecyclerView.SCROLL_STATE_IDLE */
+        int internalState = RecyclerViewOEMV1.SCROLL_STATE_IDLE;
+        switch (state) {
+            case RecyclerView.SCROLL_STATE_DRAGGING:
+                internalState = RecyclerViewOEMV1.SCROLL_STATE_DRAGGING;
+                break;
+            case RecyclerView.SCROLL_STATE_SETTLING:
+                internalState = RecyclerViewOEMV1.SCROLL_STATE_SETTLING;
+                break;
+        }
+        return internalState;
+    }
+
+    @Override
+    public int getScrollState() {
+        return toInternalScrollState(mRecyclerView.getScrollState());
+    }
+
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+        boolean rotaryScrollEnabled = contentDescription != null
+                && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+                || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
+        int orientation = getLayoutStyle() == null ? LinearLayout.VERTICAL
+                : getLayoutStyle().getOrientation();
+        initRotaryScroll(mRecyclerView, rotaryScrollEnabled, orientation);
+        // Only change this view's content description when not related to rotary scroll. Don't
+        // change its content description when related to rotary scroll, because the content
+        // description should be set on its inner recyclerview in this case.
+        if (!rotaryScrollEnabled) {
+            super.setContentDescription(contentDescription);
+        }
+    }
+
+    private OrientationHelper createOrientationHelper() {
+        if (mLayoutStyle.getOrientation() == LayoutStyleOEMV1.ORIENTATION_VERTICAL) {
+            return OrientationHelper.createVerticalHelper(mRecyclerView.getLayoutManager());
+        } else {
+            return OrientationHelper.createHorizontalHelper(mRecyclerView.getLayoutManager());
+        }
+    }
+
+    @Override
+    public int getEndAfterPadding() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getEndAfterPadding();
+    }
+
+    @Override
+    public int getStartAfterPadding() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getStartAfterPadding();
+    }
+
+    @Override
+    public int getTotalSpace() {
+        if (mLayoutStyle == null) return 0;
+        return createOrientationHelper().getTotalSpace();
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        if (mScrollBar != null) {
+            int currentPosition = findFirstVisibleItemPosition();
+            setScrollBarPadding(top, bottom);
+            // Maintain same index position after setting padding
+            scrollToPosition(currentPosition);
+        }
+        mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(),
+                top, mRecyclerView.getPaddingRight(), bottom);
+        super.setPadding(left, 0, right, 0);
+    }
+
+    @Override
+    public int getPaddingTop() {
+        return mRecyclerView.getPaddingTop();
+    }
+
+    @Override
+    public int getPaddingBottom() {
+        return mRecyclerView.getPaddingBottom();
+    }
+
+    @Override
+    public void setPaddingRelative(int start, int top, int end, int bottom) {
+        if (mScrollBar != null) {
+            int currentPosition = findFirstVisibleItemPosition();
+            setScrollBarPadding(top, bottom);
+            // Maintain same index position after setting padding
+            scrollToPosition(currentPosition);
+        }
+        mRecyclerView.setPaddingRelative(0, top, 0, bottom);
+        super.setPaddingRelative(start, 0, end, 0);
+    }
+
+    private void setScrollBarPadding(int paddingTop, int paddingBottom) {
+        if (mScrollBar != null) {
+            mScrollBar.setPadding(paddingTop, paddingBottom);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mCarUxRestrictionsUtil.register(mListener);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mCarUxRestrictionsUtil.unregister(mListener);
+    }
+
+    @Override
+    public int getRecyclerViewChildCount() {
+        if (mRecyclerView.getLayoutManager() != null) {
+            return mRecyclerView.getLayoutManager().getChildCount();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public View getRecyclerViewChildAt(int index) {
+        if (mRecyclerView.getLayoutManager() != null) {
+            return mRecyclerView.getLayoutManager().getChildAt(index);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public ViewHolderOEMV1 findViewHolderForAdapterPosition(int position) {
+        ViewHolder viewHolder = mRecyclerView.findViewHolderForAdapterPosition(position);
+        if (viewHolder instanceof ViewHolderWrapper) {
+            return ((ViewHolderWrapper) viewHolder).getViewHolder();
+        }
+        return null;
+    }
+
+    @Override
+    public ViewHolderOEMV1 findViewHolderForLayoutPosition(int position) {
+        ViewHolder viewHolder = mRecyclerView.findViewHolderForLayoutPosition(position);
+        if (viewHolder instanceof ViewHolderWrapper) {
+            return ((ViewHolderWrapper) viewHolder).getViewHolder();
+        }
+        return null;
+    }
+
+    @Override
+    public boolean canScrollHorizontally(int direction) {
+        return mRecyclerView.canScrollHorizontally(direction);
+    }
+
+    @Override
+    public boolean canScrollVertically(int direction) {
+        return mRecyclerView.canScrollVertically(direction);
+    }
+
+    @Override
+    public void addOnChildAttachStateChangeListener(
+            OnChildAttachStateChangeListenerOEMV1 listener) {
+        if (listener == null) {
+            return;
+        }
+        if (mOnChildAttachStateChangeListeners.isEmpty()) {
+            mRecyclerView.addOnChildAttachStateChangeListener(mOnChildAttachStateChangeListener);
+        }
+        mOnChildAttachStateChangeListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnChildAttachStateChangeListener(
+            OnChildAttachStateChangeListenerOEMV1 listener) {
+        if (listener == null) {
+            return;
+        }
+        mOnChildAttachStateChangeListeners.remove(listener);
+        if (mOnChildAttachStateChangeListeners.isEmpty()) {
+            mRecyclerView.removeOnChildAttachStateChangeListener(mOnChildAttachStateChangeListener);
+        }
+    }
+
+    @Override
+    public void clearOnChildAttachStateChangeListener() {
+        if (!mOnChildAttachStateChangeListeners.isEmpty()) {
+            mOnChildAttachStateChangeListeners.clear();
+            mRecyclerView.clearOnChildAttachStateChangeListeners();
+        }
+    }
+
+    @Override
+    public int getChildLayoutPosition(View child) {
+        return mRecyclerView.getChildLayoutPosition(child);
+    }
+
+    /**
+     * If this view's {@code rotaryScrollEnabled} attribute is set to true, sets the content
+     * description so that the {@code RotaryService} will treat it as a scrollable container and
+     * initializes this view accordingly.
+     */
+    private void initRotaryScroll(@NonNull ViewGroup recyclerView,
+            boolean rotaryScrollEnabled,
+            int orientation) {
+        if (rotaryScrollEnabled) {
+            setRotaryScrollEnabled(
+                    recyclerView, /* isVertical= */ orientation == LinearLayout.VERTICAL);
+        }
+
+        // If rotary scrolling is enabled, set a generic motion event listener to convert
+        // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView
+        // knows how to handle.
+        recyclerView.setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
+            if (event.getAction() == MotionEvent.ACTION_SCROLL) {
+                if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) {
+                    MotionEvent mouseEvent = MotionEvent.obtain(event);
+                    mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+                    recyclerView.onGenericMotionEvent(mouseEvent);
+                    return true;
+                }
+            }
+            return false;
+        } : null);
+
+        // If rotary scrolling is enabled, mark this view as focusable. This view will be focused
+        // when no focusable elements are visible.
+        recyclerView.setFocusable(rotaryScrollEnabled);
+
+        // Focus this view before descendants so that the RotaryService can focus this view when it
+        // wants to.
+        recyclerView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+
+        // Disable the default focus highlight. No highlight should appear when this view is
+        // focused.
+        recyclerView.setDefaultFocusHighlightEnabled(false);
+
+        // If rotary scrolling is enabled, set a focus change listener to highlight the scrollbar
+        // thumb when this recycler view is focused, i.e. when no focusable descendant is visible.
+        recyclerView.setOnFocusChangeListener(rotaryScrollEnabled ? (v, hasFocus) -> {
+            if (mScrollBar != null) mScrollBar.setHighlightThumb(hasFocus);
+        } : null);
+
+        // This recyclerView is a rotary container if it's not a scrollable container.
+        if (!rotaryScrollEnabled) {
+            recyclerView.setContentDescription(ROTARY_CONTAINER);
+        }
+    }
+
+    private static void setRotaryScrollEnabled(@NonNull View view, boolean isVertical) {
+        view.setContentDescription(
+                isVertical ? ROTARY_VERTICALLY_SCROLLABLE : ROTARY_HORIZONTALLY_SCROLLABLE);
+    }
+
+    private class UxRestrictionChangedListener implements
+            CarUxRestrictionsUtil.OnUxRestrictionsChangedListener {
+
+        @Override
+        public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+            Adapter<?> adapter = mRecyclerView.getAdapter();
+            if (adapter == null) return;
+
+            int maxItems = AdapterOEMV1.UNLIMITED;
+            if ((carUxRestrictions.getActiveRestrictions()
+                    & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT) != 0) {
+                maxItems = carUxRestrictions.getMaxCumulativeContentItems();
+            }
+
+            int originalCount = adapter.getItemCount();
+            ((AdapterWrapper) adapter).getOEMAdapter().setMaxItems(maxItems);
+            int newCount = adapter.getItemCount();
+
+            if (newCount == originalCount) {
+                return;
+            }
+
+            if (newCount < originalCount) {
+                adapter.notifyItemRangeRemoved(newCount, originalCount - newCount);
+            } else {
+                adapter.notifyItemRangeInserted(originalCount, newCount - originalCount);
+            }
+        }
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SmoothScroller.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SmoothScroller.java
new file mode 100644
index 0000000..203fe58
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SmoothScroller.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.recyclerview;
+
+import static com.chassis.car.ui.plugin.Utils.getFloat;
+
+import android.content.Context;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.chassis.car.ui.plugin.R;
+
+/**
+ * Code drop from {androidx.car.widget.CarUiSmoothScroller}
+ *
+ * <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>
+ */
+/* package */ final class SmoothScroller extends LinearSmoothScroller {
+    @VisibleForTesting
+    float mMillisecondsPerInch;
+    @VisibleForTesting
+    float mDecelerationTimeDivisor;
+    @VisibleForTesting
+    float mMillisecondsPerPixel;
+    @VisibleForTesting
+    Interpolator mInterpolator;
+    @VisibleForTesting
+    int mDensityDpi;
+
+    SmoothScroller(Context context) {
+        super(context);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mMillisecondsPerInch = getFloat(context.getResources(),
+                R.dimen.scrollbar_milliseconds_per_inch);
+        mDecelerationTimeDivisor = getFloat(context.getResources(),
+                R.dimen.scrollbar_deceleration_times_divisor);
+        mInterpolator =
+                new DecelerateInterpolator(
+                        getFloat(context.getResources(),
+                                R.dimen.scrollbar_decelerate_interpolator_factor));
+        mDensityDpi = context.getResources().getDisplayMetrics().densityDpi;
+        mMillisecondsPerPixel = mMillisecondsPerInch / mDensityDpi;
+    }
+
+    @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 int calculateTimeForScrolling(int dx) {
+        return (int) Math.ceil(Math.abs(dx) * mMillisecondsPerPixel);
+    }
+
+    @Override
+    protected int calculateTimeForDeceleration(int dx) {
+        return (int) Math.ceil(calculateTimeForScrolling(dx) / mDecelerationTimeDivisor);
+    }
+}
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SnapHelper.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SnapHelper.java
new file mode 100644
index 0000000..26db0c8
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/recyclerview/SnapHelper.java
@@ -0,0 +1,465 @@
+/*
+ * 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.chassis.car.ui.plugin.recyclerview;
+
+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;
+
+import java.util.Objects;
+
+/**
+ * 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.
+ */
+/* package */ final class SnapHelper extends LinearSnapHelper {
+    /**
+     * The percentage of a View that needs to be completely visible for it to be a viable snap
+     * target.
+     */
+    private static final float VIEW_VISIBLE_THRESHOLD = 0.5f;
+
+    /**
+     * When a View is longer than containing RecyclerView, the percentage of the end of this View
+     * that needs to be completely visible to prevent the rest of views to be a viable snap target.
+     *
+     * <p>In other words, if a longer-than-screen View takes more than threshold screen space on its
+     * end, do not snap to any View.
+     */
+    private static final float LONG_ITEM_END_VISIBLE_THRESHOLD = 0.3f;
+
+    private final Context mContext;
+    @Nullable
+    private RecyclerView mRecyclerView;
+
+    SnapHelper(Context context) {
+        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];
+
+        // Don't snap when not in touch mode, i.e. when using rotary.
+        if (!mRecyclerView.isInTouchMode()) {
+            return out;
+        }
+
+        if (layoutManager.canScrollHorizontally()) {
+            out[0] = distanceToTopMargin(targetView, getHorizontalHelper(layoutManager));
+        }
+
+        if (layoutManager.canScrollVertically()) {
+            out[1] = distanceToTopMargin(targetView, getVerticalHelper(layoutManager));
+        }
+
+        return out;
+    }
+
+    /**
+     * 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 LayoutManager} for the attached RecyclerView.
+     * @return The View closest to the start of the RecyclerView. Returns {@code null}when:
+     * <ul>
+     *     <li>there is no item; or
+     *     <li>no visible item can fully fit in the containing RecyclerView; or
+     *     <li>an item longer than containing RecyclerView is about to scroll out.
+     * </ul>
+     */
+    @Override
+    @Nullable
+    public View findSnapView(LayoutManager layoutManager) {
+        int childCount = layoutManager.getChildCount();
+        if (childCount == 0) {
+            return null;
+        }
+
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+
+        // If there's only one child, then that will be the snap target.
+        if (childCount == 1) {
+            View firstChild = layoutManager.getChildAt(0);
+            return isValidSnapView(firstChild, orientationHelper) ? firstChild : null;
+        }
+
+        if (mRecyclerView == null) {
+            return null;
+        }
+
+        // If the top child view is longer than the RecyclerView (long item), and it's not yet
+        // scrolled out - meaning the screen it takes up is more than threshold,
+        // do not snap to any view.
+        // This way avoids next View snapping to top "pushes" out the end of a long item.
+        View firstChild = mRecyclerView.getChildAt(0);
+        if (firstChild.getHeight() > mRecyclerView.getHeight()
+                // Long item start is scrolled past screen;
+                && orientationHelper.getDecoratedStart(firstChild) < 0
+                // and it takes up more than threshold screen size.
+                && orientationHelper.getDecoratedEnd(firstChild) > (
+                mRecyclerView.getHeight() * LONG_ITEM_END_VISIBLE_THRESHOLD)) {
+            return null;
+        }
+
+        @NonNull View lastVisibleChild = Objects.requireNonNull(
+                layoutManager.getChildAt(childCount - 1));
+
+        // Check if the last child visible is the last item in the list.
+        boolean lastItemVisible =
+                layoutManager.getPosition(lastVisibleChild) == layoutManager.getItemCount() - 1;
+
+        // If it is, then check how much of that view is visible.
+        float lastItemPercentageVisible = lastItemVisible
+                ? getPercentageVisible(lastVisibleChild, orientationHelper) : 0;
+
+        View closestChild = null;
+        int closestDistanceToStart = Integer.MAX_VALUE;
+        float closestPercentageVisible = 0.f;
+
+        // Iterate to find the child closest to the top and more than half way visible.
+        for (int i = 0; i < childCount; i++) {
+            View child = layoutManager.getChildAt(i);
+            int startOffset = orientationHelper.getDecoratedStart(child);
+
+            if (Math.abs(startOffset) < closestDistanceToStart) {
+                float percentageVisible = getPercentageVisible(child, orientationHelper);
+
+                if (percentageVisible > VIEW_VISIBLE_THRESHOLD
+                        && percentageVisible > closestPercentageVisible) {
+                    closestDistanceToStart = startOffset;
+                    closestChild = child;
+                    closestPercentageVisible = percentageVisible;
+                }
+            }
+        }
+
+        View childToReturn = closestChild;
+
+        // If closestChild is null, then that means we were unable to find a closest child that
+        // is over the VIEW_VISIBLE_THRESHOLD. This could happen if the views are larger than
+        // the given area. In this case, consider returning the lastVisibleChild so that the screen
+        // scrolls. Also, check if the last item should be displayed anyway if it is mostly visible.
+        if ((childToReturn == null
+                || (lastItemVisible && lastItemPercentageVisible > closestPercentageVisible))) {
+            childToReturn = lastVisibleChild;
+        }
+
+        // Return null if the childToReturn is not valid. This allows the user to scroll freely
+        // with no snapping. This can allow them to see the entire view.
+        return isValidSnapView(childToReturn, orientationHelper) ? childToReturn : null;
+    }
+
+    private static int distanceToTopMargin(@NonNull View targetView, OrientationHelper helper) {
+        final int childTop = helper.getDecoratedStart(targetView);
+        final int containerTop = helper.getStartAfterPadding();
+        return childTop - containerTop;
+    }
+
+    /**
+     * Returns whether or not the given View is a valid snapping view. A view is considered valid
+     * for snapping if it can fit entirely within the height of the RecyclerView it is contained
+     * within.
+     *
+     * <p>If the view is larger than the RecyclerView, then it might not want to be snapped to
+     * to allow the user to scroll and see the rest of the View.
+     *
+     * @param view   The view to determine the snapping potential.
+     * @param helper The {@link OrientationHelper} associated with the current RecyclerView.
+     * @return {@code true} if the given view is a valid snapping view; {@code false} otherwise.
+     */
+    private static boolean isValidSnapView(View view, OrientationHelper helper) {
+        return helper.getDecoratedMeasurement(view) <= helper.getTotalSpace();
+    }
+
+    /**
+     * 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.
+     */
+    static float getPercentageVisible(View view, OrientationHelper helper) {
+        int start = helper.getStartAfterPadding();
+        int end = helper.getEndAfterPadding();
+
+        int viewStart = helper.getDecoratedStart(view);
+        int viewEnd = helper.getDecoratedEnd(view);
+
+        if (viewStart >= start && viewEnd <= end) {
+            // The view is within the bounds of the RecyclerView, so it's fully visible.
+            return 1.f;
+        } else if (viewEnd <= start) {
+            // The view is above the visible area of the RecyclerView.
+            return 0;
+        } else if (viewStart >= end) {
+            // The view is below the visible area of the RecyclerView.
+            return 0;
+        } else if (viewStart <= start && viewEnd >= end) {
+            // The view is larger than the height of the RecyclerView.
+            return ((float) end - start) / helper.getDecoratedMeasurement(view);
+        } else if (viewStart < start) {
+            // The view is above the start of the RecyclerView.
+            return ((float) viewEnd - start) / helper.getDecoratedMeasurement(view);
+        } else {
+            // The view is below the end of the RecyclerView.
+            return ((float) end - viewStart) / helper.getDecoratedMeasurement(view);
+        }
+    }
+
+    @Override
+    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+        super.attachToRecyclerView(recyclerView);
+        mRecyclerView = recyclerView;
+    }
+
+    /**
+     * Returns a scroller specific to this {@code PagedSnapHelper}. This scroller is used for all
+     * smooth scrolling operations, including flings.
+     *
+     * @param layoutManager The {@link LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @return a {@link RecyclerView.SmoothScroller} which will handle the scrolling.
+     */
+    @Override
+    protected RecyclerView.SmoothScroller createScroller(@NonNull LayoutManager layoutManager) {
+        return new SmoothScroller(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;
+        }
+
+        LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return outDist;
+        }
+
+        int lastChildPosition = isAtEnd(layoutManager) ? 0 : layoutManager.getChildCount() - 1;
+
+        OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
+        @NonNull View lastChild = Objects.requireNonNull(
+                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;
+    }
+
+    /**
+     * Estimates a position to which SnapHelper will try to snap to for a requested scroll
+     * distance.
+     *
+     * @param helper         The {@link OrientationHelper} that is created from the LayoutManager.
+     * @param scrollDistance The intended scroll distance.
+     *
+     * @return The diff between the target snap position and the current position.
+     */
+    public int estimateNextPositionDiffForScrollDistance(OrientationHelper helper,
+            int scrollDistance) {
+        float distancePerChild = computeDistancePerChild(helper.getLayoutManager(), helper);
+        if (distancePerChild <= 0) {
+            return 0;
+        }
+        return Math.round(scrollDistance / distancePerChild);
+    }
+
+    /**
+     * This method is taken verbatim from the [androidx] {@link LinearSnapHelper} private method
+     * implementation.
+     *
+     * Computes an average pixel value to pass a single child.
+     * <p>
+     * Returns a negative value if it cannot be calculated.
+     *
+     * @param layoutManager The {@link LayoutManager} associated with the attached
+     *                      {@link RecyclerView}.
+     * @param helper        The relevant {@link OrientationHelper} for the attached
+     *                      {@link LayoutManager}.
+     *
+     * @return A float value that is the average number of pixels needed to scroll by one view in
+     * the relevant direction.
+     */
+    private float computeDistancePerChild(LayoutManager layoutManager,
+                                          OrientationHelper helper) {
+        View minPosView = null;
+        View maxPosView = null;
+        int minPos = Integer.MAX_VALUE;
+        int maxPos = Integer.MIN_VALUE;
+        int childCount = layoutManager.getChildCount();
+        if (childCount == 0) {
+            return 1;
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            View child = layoutManager.getChildAt(i);
+            final int pos = layoutManager.getPosition(child);
+            if (pos == RecyclerView.NO_POSITION) {
+                continue;
+            }
+            if (pos < minPos) {
+                minPos = pos;
+                minPosView = child;
+            }
+            if (pos > maxPos) {
+                maxPos = pos;
+                maxPosView = child;
+            }
+        }
+        if (minPosView == null || maxPosView == null) {
+            return 1;
+        }
+        int start = Math.min(helper.getDecoratedStart(minPosView),
+                helper.getDecoratedStart(maxPosView));
+        int end = Math.max(helper.getDecoratedEnd(minPosView),
+                helper.getDecoratedEnd(maxPosView));
+        int distance = end - start;
+        if (distance == 0) {
+            return 0;
+        }
+        return 1f * distance / ((maxPos - minPos) + 1);
+    }
+
+    /**
+     * Returns {@code true} if the RecyclerView is completely displaying the first item.
+     */
+    public boolean isAtStart(@Nullable LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        @NonNull View firstChild = Objects.requireNonNull(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(@Nullable LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        int childCount = layoutManager.getChildCount();
+        OrientationHelper orientationHelper =
+                layoutManager.canScrollVertically() ? getVerticalHelper(layoutManager)
+                        : getHorizontalHelper(layoutManager);
+
+        @NonNull View lastVisibleChild = Objects.requireNonNull(
+                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 LayoutManager}.
+     */
+    @NonNull
+    private OrientationHelper getOrientationHelper(@NonNull LayoutManager layoutManager) {
+        return layoutManager.canScrollVertically()
+                ? getVerticalHelper(layoutManager)
+                : getHorizontalHelper(layoutManager);
+    }
+
+    @NonNull
+    private OrientationHelper getVerticalHelper(@NonNull LayoutManager layoutManager) {
+        if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) {
+            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
+        }
+        return mVerticalHelper;
+    }
+
+    @NonNull
+    private OrientationHelper getHorizontalHelper(@NonNull 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-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/BaseLayoutInstaller.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/BaseLayoutInstaller.java
similarity index 86%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/BaseLayoutInstaller.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/BaseLayoutInstaller.java
index e5dcb80..62cf177 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/BaseLayoutInstaller.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/BaseLayoutInstaller.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.content.Context;
 import android.view.LayoutInflater;
@@ -26,28 +26,30 @@
 import androidx.annotation.LayoutRes;
 import androidx.annotation.Nullable;
 
-import com.android.car.ui.sharedlibrary.oemapis.FocusAreaOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.FocusParkingViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.InsetsOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.FocusAreaOEMV1;
+import com.android.car.ui.plugin.oemapis.FocusParkingViewOEMV1;
+import com.android.car.ui.plugin.oemapis.InsetsOEMV1;
+import com.android.car.ui.plugin.oemapis.PluginFactoryOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
+import com.chassis.car.ui.plugin.R;
 
 import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
  * A helper class for implementing installBaseLayoutAround from
- * {@link com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1}
+ * {@link PluginFactoryOEMV1}
  */
 @SuppressWarnings("AndroidJdkLibsChecker")
 public class BaseLayoutInstaller {
     /**
      * Implementation of installBaseLayoutAround from
-     * {@link com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1}
+     * {@link PluginFactoryOEMV1}
      */
     public static ToolbarControllerOEMV1 installBaseLayoutAround(
-            Context sharedLibraryContext,
+            Context sourceContext,
+            Context pluginContext,
             View contentView,
             Consumer<InsetsOEMV1> insetsChangedListener,
             boolean toolbarEnabled,
@@ -55,18 +57,16 @@
             @Nullable Function<Context, FocusParkingViewOEMV1> focusParkingViewFactory,
             @Nullable Function<Context, FocusAreaOEMV1> focusAreaFactory) {
 
-        Context activityContext = contentView.getContext();
-
-        // Add the configuration from the activity context to the shared library context,
-        // or else when inflating views with the shared library context, they won't have access
+        // Add the configuration from the source context to the plugin context,
+        // or else when inflating views with the plugin context, they won't have access
         // to stuff like the screen size. It will also cause a StrictMode violation without this.
-        sharedLibraryContext = sharedLibraryContext.createConfigurationContext(
-                activityContext.getResources().getConfiguration());
+        pluginContext = pluginContext.createConfigurationContext(
+                sourceContext.getResources().getConfiguration());
 
         @LayoutRes int layout = toolbarEnabled
                 ? R.layout.base_layout_toolbar
                 : R.layout.base_layout;
-        FrameLayout baseLayout = (FrameLayout) LayoutInflater.from(sharedLibraryContext).inflate(
+        FrameLayout baseLayout = (FrameLayout) LayoutInflater.from(pluginContext).inflate(
                 layout, null, false);
 
         // Replace the app's content view with a base layout
@@ -83,10 +83,10 @@
                 ViewGroup.LayoutParams.MATCH_PARENT));
 
         // Add FocusParkingView to base layout.
-        // Make sure to use the application context here, not the shared library context,
+        // Make sure to use the activity context here, not the plugin context,
         // as the implementation of FocusParkingView/FocusArea is in the static car-ui-lib.
         if (focusParkingViewFactory != null) {
-            View focusParkingView = focusParkingViewFactory.apply(activityContext).getView();
+            View focusParkingView = focusParkingViewFactory.apply(sourceContext).getView();
             if (focusParkingView != null) {
                 baseLayout.addView(focusParkingView, 0,
                         new FrameLayout.LayoutParams(
@@ -100,10 +100,10 @@
         // layout inflater factory and specifying it in XML, because a LayoutInflater will
         // use a parent view's context for creating all the child views. We have to create
         // the FocusArea using the app's context, so that it can access it's resources,
-        // but we want children of the FocusArea to use the shared library context, so we can
-        // access shared library resources.
+        // but we want children of the FocusArea to use the plugin context, so we can
+        // access plugin resources.
         if (focusAreaFactory != null && toolbarEnabled) {
-            LinearLayout focusArea = focusAreaFactory.apply(activityContext).getView();
+            LinearLayout focusArea = focusAreaFactory.apply(sourceContext).getView();
             if (focusArea != null) {
                 View toolbar = baseLayout.requireViewById(R.id.toolbar_background);
                 int toolbarIndex = baseLayout.indexOfChild(toolbar);
@@ -118,7 +118,7 @@
         ToolbarControllerOEMV1 toolbarController = null;
         if (toolbarEnabled) {
             toolbarController = new ToolbarControllerImpl(
-                    baseLayout, sharedLibraryContext, activityContext);
+                    baseLayout, pluginContext, sourceContext);
         }
 
         InsetsUpdater updater = new InsetsUpdater(baseLayout, contentView);
@@ -136,10 +136,10 @@
         // These tags mark views that should overlay the content view in the base layout.
         // Apps will then be able to draw under these views, but will be encouraged to not put
         // any user-interactable content there.
-        private static final String LEFT_INSET_TAG = "shared_lib_left_inset";
-        private static final String RIGHT_INSET_TAG = "shared_lib_right_inset";
-        private static final String TOP_INSET_TAG = "shared_lib_top_inset";
-        private static final String BOTTOM_INSET_TAG = "shared_lib_bottom_inset";
+        private static final String LEFT_INSET_TAG = "plugin_left_inset";
+        private static final String RIGHT_INSET_TAG = "plugin_right_inset";
+        private static final String TOP_INSET_TAG = "plugin_top_inset";
+        private static final String BOTTOM_INSET_TAG = "plugin_bottom_inset";
 
         private final View mContentView;
         private final View mContentViewContainer; // Equivalent to mContentView except in Media
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ClickBlockingView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ClickBlockingView.java
similarity index 98%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ClickBlockingView.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ClickBlockingView.java
index 77cc7f5..3e64163 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ClickBlockingView.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ClickBlockingView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/MenuItemView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/MenuItemView.java
similarity index 88%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/MenuItemView.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/MenuItemView.java
index 2831d22..0d97293 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/MenuItemView.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/MenuItemView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -27,10 +27,10 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.MenuItemOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
-import com.chassis.car.ui.sharedlibrary.uxr.DrawableStateView;
+import com.chassis.car.ui.plugin.R;
+import com.chassis.car.ui.plugin.uxr.DrawableStateView;
 
 class MenuItemView extends FrameLayout {
     private static final int[] RESTRICTED_STATE = new int[] {R.attr.state_ux_restricted};
@@ -65,13 +65,10 @@
         mTextWithIconView = requireViewById(R.id.car_ui_toolbar_menu_item_text_with_icon);
 
         mMenuItem = menuItem;
-        mMenuItem.setUpdateListener(this::onMenuItemUpdated);
         onMenuItemUpdated(mMenuItem);
     }
 
     public void onMenuItemUpdated(MenuItemOEMV1 menuItem) {
-        setId(mMenuItem.getId());
-
         boolean hasIcon = mMenuItem.getIcon() != null;
         boolean hasText = !TextUtils.isEmpty(mMenuItem.getTitle());
         boolean textAndIcon = mMenuItem.isShowingIconAndTitle();
@@ -114,11 +111,17 @@
         recursiveSetEnabledAndDrawableState(this);
         setActivated(mMenuItem.isActivated());
 
-        clickTarget.setOnClickListener(v -> {
-            if (mMenuItem.isEnabled()) {
-                mMenuItem.performClick();
-            }
-        });
+        Runnable onClickListener = mMenuItem.getOnClickListener();
+        if (onClickListener != null) {
+            clickTarget.setOnClickListener(v -> {
+                if (mMenuItem.isEnabled()) {
+                    onClickListener.run();
+                }
+            });
+        } else {
+            clickTarget.setOnClickListener(null);
+            clickTarget.setClickable(false);
+        }
     }
 
     private void recursiveSetEnabledAndDrawableState(View view) {
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OnPrivateImeCommandEditText.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OnPrivateImeCommandEditText.java
similarity index 94%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OnPrivateImeCommandEditText.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OnPrivateImeCommandEditText.java
index 07bd2fc..4c41c9b 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OnPrivateImeCommandEditText.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OnPrivateImeCommandEditText.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Bundle;
 import android.util.AttributeSet;
@@ -27,6 +28,7 @@
  * {@link #setOnPrivateImeCommandListener} argument. This allows listening to calls to
  * {@link android.widget.TextView#onPrivateIMECommand(String, Bundle)}.
  */
+@SuppressLint("AppCompatCustomView")
 public class OnPrivateImeCommandEditText extends EditText {
 
     private BiConsumer<String, Bundle> mOnAppPrivateCommandListener;
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OverflowMenuItem.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OverflowMenuItem.java
new file mode 100644
index 0000000..39c7294
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/OverflowMenuItem.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.toolbar;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1;
+
+import com.chassis.car.ui.plugin.R;
+
+import java.util.Collections;
+import java.util.List;
+
+class OverflowMenuItem {
+    private static final int DIALOG_ANIMATION_TIME = 250;
+
+    @NonNull
+    private final Context mPluginContext;
+
+    @NonNull
+    private List<MenuItemOEMV1> mOverflowMenuItems = Collections.emptyList();
+
+    private MenuItemOEMV1 mMenuItem;
+
+    private LinearLayout mItemsContainer;
+    private View mDialogBackground;
+
+
+    OverflowMenuItem(
+            @NonNull Context pluginContext,
+            @NonNull ViewStub dialogStub) {
+        mPluginContext = pluginContext;
+
+        mMenuItem = MenuItemOEMV1.builder()
+                .setTitle(pluginContext.getString(R.string.toolbar_menu_item_overflow_title))
+                .setIcon(ContextCompat.getDrawable(
+                        pluginContext, R.drawable.toolbar_menu_item_overflow))
+                .setVisible(false)
+                .setOnClickListener(() -> {
+                    if (mItemsContainer == null) {
+                        mDialogBackground = dialogStub.inflate();
+                        mItemsContainer = mDialogBackground
+                                .requireViewById(R.id.toolbar_dialog_linear_layout);
+                        mDialogBackground.requireViewById(R.id.toolbar_dialog_dismiss_button)
+                                .setOnClickListener(v -> hideDialog());
+                        mDialogBackground.requireViewById(R.id.toolbar_dialog_shade)
+                                .setOnClickListener(v -> hideDialog());
+                    }
+
+                    refreshViews();
+
+                    mDialogBackground.setVisibility(View.VISIBLE);
+                    mDialogBackground.setAlpha(0);
+                    mDialogBackground.animate()
+                            .alpha(1)
+                            .setDuration(DIALOG_ANIMATION_TIME)
+                            .setListener(null)
+                            .start();
+                })
+                .build();
+    }
+
+    private void hideDialog() {
+        if (mDialogBackground.getVisibility() == View.GONE) {
+            return;
+        }
+        mDialogBackground.animate()
+                .alpha(0)
+                .setDuration(DIALOG_ANIMATION_TIME)
+                .setListener(
+                        new Animator.AnimatorListener() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                mDialogBackground.setVisibility(View.GONE);
+                            }
+
+                            @Override
+                            public void onAnimationCancel(Animator animation) {
+                                mDialogBackground.setVisibility(View.GONE);
+                            }
+
+                            @Override
+                            public void onAnimationRepeat(Animator animation) {
+                            }
+                        })
+                .start();
+    }
+
+    private void refreshViews() {
+        if (mItemsContainer == null) {
+            return;
+        }
+
+        // Make mItemsContainer have a child toolbar_overflow_item for each overflow menu item.
+        if (mItemsContainer.getChildCount() > mOverflowMenuItems.size()) {
+            mItemsContainer.removeViews(mOverflowMenuItems.size(),
+                    mItemsContainer.getChildCount() - mOverflowMenuItems.size());
+        }
+        while (mItemsContainer.getChildCount() < mOverflowMenuItems.size()) {
+            LayoutInflater.from(mPluginContext).inflate(
+                    R.layout.toolbar_overflow_item, mItemsContainer, true);
+        }
+
+        for (int i = 0; i < mOverflowMenuItems.size(); i++) {
+            bindItemView(mItemsContainer.getChildAt(i), mOverflowMenuItems.get(i));
+        }
+    }
+
+    private void bindItemView(View view, MenuItemOEMV1 item) {
+        TextView titleView = view.requireViewById(R.id.toolbar_overflow_item_title);
+        titleView.setText(item.getTitle());
+
+        Runnable onClickListener = item.getOnClickListener();
+        if (onClickListener != null) {
+            view.setOnClickListener(v -> {
+                hideDialog();
+                onClickListener.run();
+            });
+        } else {
+            view.setOnClickListener(null);
+            view.setClickable(false);
+        }
+    }
+
+    public MenuItemOEMV1 getMenuItem() {
+        return mMenuItem;
+    }
+
+    public void setOverflowMenuItems(List<MenuItemOEMV1> menuItems) {
+        mOverflowMenuItems = menuItems;
+        mMenuItem = mMenuItem.copy().setVisible(!menuItems.isEmpty()).build();
+
+        if (mDialogBackground != null && mDialogBackground.getVisibility() == View.VISIBLE) {
+            refreshViews();
+        }
+    }
+}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ProgressBarController.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ProgressBarController.java
similarity index 91%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ProgressBarController.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ProgressBarController.java
index f04ad13..5ace623 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ProgressBarController.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ProgressBarController.java
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.view.View;
 import android.widget.ProgressBar;
 
 import androidx.annotation.NonNull;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ProgressBarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ProgressBarControllerOEMV1;
 
 class ProgressBarController implements ProgressBarControllerOEMV1 {
     private final ProgressBar mProgressBar;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/SearchController.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/SearchController.java
similarity index 84%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/SearchController.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/SearchController.java
index 47d255a..2c424ac 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/SearchController.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/SearchController.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
+import android.car.drivingstate.CarUxRestrictions;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -35,9 +36,10 @@
 import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
+import com.chassis.car.ui.plugin.R;
+import com.chassis.car.ui.plugin.uxr.CarUxRestrictionsUtil;
 
 import java.util.Objects;
 import java.util.function.BiConsumer;
@@ -54,6 +56,7 @@
     private ImageView mSearchIconView;
     private OnPrivateImeCommandEditText mEditText;
     private View mCloseIcon;
+    private boolean mIsRestricted;
 
     private int mStartPaddingWithoutIcon;
     private int mStartPadding;
@@ -73,6 +76,9 @@
     private Consumer<TextView> mSearchTextViewConsumer;
     @Nullable
     private BiConsumer<String, Bundle> mOnPrivateImeCommandListener;
+    @NonNull
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new UxRestrictionChangedListener();
 
     private final TextWatcher mTextWatcher = new TextWatcher() {
         @Override
@@ -95,6 +101,7 @@
         mStub = Objects.requireNonNull(searchStub);
         mSearchIcon = getDrawable(R.drawable.icon_search);
         mSearchHint = mStub.getContext().getString(R.string.toolbar_default_search_hint);
+        CarUxRestrictionsUtil.getInstance(mStub.getContext()).register(mListener);
     }
 
     public void setSearchTextViewConsumer(@Nullable Consumer<TextView> textViewConsumer) {
@@ -171,6 +178,10 @@
             mEndPadding = resources.getDimensionPixelSize(
                     R.dimen.toolbar_search_close_icon_container_width);
             mEditText.setPaddingRelative(mStartPadding, 0, mEndPadding, 0);
+
+            if (mIsRestricted) {
+                enableRestriction();
+            }
         }
 
         boolean showingSearch = !mInflatedView.isShown()
@@ -219,7 +230,7 @@
         }
         mSearchHint = hint;
 
-        if (mEditText != null) {
+        if (mEditText != null && !mIsRestricted) {
             mEditText.setHint(hint);
         }
     }
@@ -267,4 +278,42 @@
         }
         return result;
     }
+
+    private void enableRestriction() {
+        mIsRestricted = true;
+
+        if (mEditText == null) {
+            return;
+        }
+
+        mEditText.setHint(mEditText.getContext().getString(
+                R.string.toolbar_ux_restricted_search_hint));
+        mEditText.setEnabled(false);
+    }
+
+    private void disableRestriction() {
+        mIsRestricted = false;
+
+        if (mEditText == null) {
+            return;
+        }
+
+        mEditText.setHint(mSearchHint);
+        mEditText.setEnabled(true);
+    }
+
+    private class UxRestrictionChangedListener implements
+            CarUxRestrictionsUtil.OnUxRestrictionsChangedListener {
+
+        @Override
+        public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+            boolean isKeyboardRestricted = (carUxRestrictions.getActiveRestrictions()
+                    & CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD) != 0;
+            if (isKeyboardRestricted) {
+                enableRestriction();
+            } else if (!isKeyboardRestricted) {
+                disableRestriction();
+            }
+        }
+    }
 }
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/TabLayout.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/TabLayout.java
similarity index 88%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/TabLayout.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/TabLayout.java
index 868193d..ff50190 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/TabLayout.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/TabLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -26,9 +26,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.TabOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.TabOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
+import com.chassis.car.ui.plugin.R;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -39,7 +39,7 @@
  * A view that can show tabs via the {@link #setTabs(List, int)} method.
  */
 public class TabLayout extends LinearLayout {
-    private List<? extends TabOEMV1> mTabs = new ArrayList<>();
+    private List<TabOEMV1> mTabs = new ArrayList<>();
     private TabOEMV1 mSelectedTab;
     private final Map<TabOEMV1, View> mTabViews = new HashMap<>();
 
@@ -61,7 +61,7 @@
      * @param tabs The tabs to show.
      * @param selectedTab Which tab is selected.
      */
-    public void setTabs(@NonNull List<? extends TabOEMV1> tabs, int selectedTab) {
+    public void setTabs(@NonNull List<TabOEMV1> tabs, int selectedTab) {
         removeAllViews();
         mTabViews.clear();
         mSelectedTab = null;
@@ -89,11 +89,17 @@
         TextView textView = view.requireViewById(R.id.car_ui_toolbar_tab_item_text);
 
         view.setOnClickListener(v -> selectTab(tab, true));
+        if (tab.isTinted()) {
+            iconView.setImageTintList(getContext()
+                    .getColorStateList(R.color.toolbar_tab_item_selector));
+        } else {
+            iconView.setImageTintList(null);
+        }
         iconView.setImageDrawable(tab.getIcon());
-        textView.setText(tab.getTitle());
 
         boolean selected = tab == mSelectedTab;
         view.setActivated(selected);
+        textView.setText(tab.getTitle());
         textView.setTextAppearance(selected
                 ? R.style.TextAppearance_CarUi_Widget_Toolbar_Tab_Selected
                 : R.style.TextAppearance_CarUi_Widget_Toolbar_Tab);
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ToolbarControllerImpl.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ToolbarControllerImpl.java
similarity index 83%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ToolbarControllerImpl.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ToolbarControllerImpl.java
index fdecb60..3d93083 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/ToolbarControllerImpl.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/toolbar/ToolbarControllerImpl.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.toolbar;
+package com.chassis.car.ui.plugin.toolbar;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
@@ -26,14 +26,15 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
 
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ImeSearchInterfaceOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.MenuItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ProgressBarControllerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.TabOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ImeSearchInterfaceOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.MenuItemOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ProgressBarControllerOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.TabOEMV1;
+import com.android.car.ui.plugin.oemapis.toolbar.ToolbarControllerOEMV1;
 
-import com.chassis.car.ui.sharedlibrary.R;
+import com.chassis.car.ui.plugin.R;
 
 import java.util.Collections;
 import java.util.List;
@@ -45,7 +46,7 @@
 @SuppressWarnings("AndroidJdkLibsChecker")
 class ToolbarControllerImpl implements ToolbarControllerOEMV1 {
 
-    private final Context mSharedLibraryContext;
+    private final Context mPluginContext;
     private final ImageView mBackButtonView;
     private final TextView mTitleView;
     private final TextView mSubtitleView;
@@ -56,6 +57,7 @@
     private final ViewGroup mMenuItemsContainer;
     private final SearchController mSearchController;
     private final ViewGroup mNavIconContainer;
+    private final View mBackground;
 
     private final boolean mTitleAndTabsMutuallyExclusive;
     private final boolean mLogoFillsNavSpace;
@@ -69,8 +71,8 @@
     private final OverflowMenuItem mOverflowMenuItem;
 
 
-    ToolbarControllerImpl(View view, Context sharedLibraryContext, Context activityContext) {
-        mSharedLibraryContext = sharedLibraryContext;
+    ToolbarControllerImpl(View view, Context pluginContext, Context sourceContext) {
+        mPluginContext = pluginContext;
         mBackButtonView = view.requireViewById(R.id.toolbar_nav_icon);
         mNavIconContainer = view.requireViewById(R.id.toolbar_nav_icon_container);
         mTitleView = view.requireViewById(R.id.toolbar_title);
@@ -81,15 +83,17 @@
                 view.requireViewById(R.id.toolbar_progress_bar));
         mTabContainer = view.requireViewById(R.id.toolbar_tabs);
         mMenuItemsContainer = view.requireViewById(R.id.toolbar_menu_items_container);
+        mBackground = view.requireViewById(R.id.toolbar_background);
         mSearchController = new SearchController(
                 view.requireViewById(R.id.toolbar_search_view_stub));
-        mOverflowMenuItem = new OverflowMenuItem(sharedLibraryContext, activityContext);
+        mOverflowMenuItem = new OverflowMenuItem(pluginContext,
+                view.requireViewById(R.id.toolbar_dialog_stub));
 
-        mTitleAndTabsMutuallyExclusive = sharedLibraryContext.getResources()
+        mTitleAndTabsMutuallyExclusive = pluginContext.getResources()
                 .getBoolean(R.bool.toolbar_title_and_tabs_mutually_exclusive);
-        mLogoFillsNavSpace = sharedLibraryContext.getResources()
+        mLogoFillsNavSpace = pluginContext.getResources()
                 .getBoolean(R.bool.toolbar_logo_fills_nav_icon_space);
-        mNavIconSpaceReserved = sharedLibraryContext.getResources()
+        mNavIconSpaceReserved = pluginContext.getResources()
                 .getBoolean(R.bool.toolbar_nav_icon_space_reserved);
     }
 
@@ -124,7 +128,7 @@
     }
 
     @Override
-    public void setTabs(List<? extends TabOEMV1> tabs, int selectedTab) {
+    public void setTabs(List<TabOEMV1> tabs, int selectedTab) {
         if (tabs == null) {
             tabs = Collections.emptyList();
         }
@@ -208,7 +212,7 @@
             mNavIconContainer.setClickable(mBackButtonVisible);
 
             mNavIconContainer.setContentDescription(mBackButtonVisible
-                    ? mSharedLibraryContext
+                    ? mPluginContext
                         .getString(R.string.toolbar_nav_icon_content_description)
                     : null);
             update();
@@ -228,26 +232,26 @@
     }
 
     @Override
-    public void setMenuItems(List<? extends MenuItemOEMV1> menuItems) {
+    public void setMenuItems(List<MenuItemOEMV1> menuItems) {
         if (menuItems == null) {
             menuItems = Collections.emptyList();
         }
 
-        List<? extends MenuItemOEMV1> overflowMenuItems = menuItems.stream()
+        List<MenuItemOEMV1> overflowMenuItems = menuItems.stream()
                 .filter(i -> i.getDisplayBehavior() != MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS)
                 .collect(Collectors.toList());
 
-        List<? extends MenuItemOEMV1> regularMenuItems = Stream.concat(
-                menuItems.stream(),
-                Stream.of(mOverflowMenuItem))
-                .filter(i -> i.getDisplayBehavior() == MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS)
-                .collect(Collectors.toList());
-
         mOverflowMenuItem.setOverflowMenuItems(overflowMenuItems);
 
+        List<MenuItemOEMV1> regularMenuItems = Stream.concat(
+                menuItems.stream(),
+                Stream.of(mOverflowMenuItem.getMenuItem()))
+                .filter(i -> i.getDisplayBehavior() == MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS)
+                .collect(Collectors.toList());
+
         mMenuItemsContainer.removeAllViews();
         for (MenuItemOEMV1 menuItem : regularMenuItems) {
-            MenuItemView menuItemView = new MenuItemView(mSharedLibraryContext, menuItem);
+            MenuItemView menuItemView = new MenuItemView(mPluginContext, menuItem);
             mMenuItemsContainer.addView(menuItemView,
                     new LinearLayout.LayoutParams(
                             LinearLayout.LayoutParams.WRAP_CONTENT,
@@ -275,6 +279,16 @@
         return mProgressBar;
     }
 
+    @Override
+    public void setBackgroundShown(boolean shown) {
+        if (shown) {
+            mBackground.setBackground(
+                    AppCompatResources.getDrawable(mPluginContext, R.drawable.toolbar_background));
+        } else {
+            mBackground.setBackground(null);
+        }
+    }
+
     private void update() {
         boolean isSearching = mSearchMode != ToolbarControllerOEMV1.SEARCH_MODE_DISABLED;
         boolean hasTabs = mTabContainer.hasTabs();
diff --git a/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/CarUxRestrictionsUtil.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/CarUxRestrictionsUtil.java
new file mode 100644
index 0000000..2d6dc7e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/CarUxRestrictionsUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.chassis.car.ui.plugin.uxr;
+
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Utility class to access {@link CarUxRestrictionsManager}.
+ *
+ * <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";
+
+    @NonNull
+    private CarUxRestrictions mCarUxRestrictions = getDefaultRestrictions();
+
+    private final Set<OnUxRestrictionsChangedListener> mObservers =
+            Collections.newSetFromMap(new WeakHashMap<>());
+    private static CarUxRestrictionsUtil sInstance;
+
+    private CarUxRestrictionsUtil(Context context) {
+        CarUxRestrictionsManager.OnUxRestrictionsChangedListener listener =
+                (carUxRestrictions) -> {
+                    if (carUxRestrictions == null) {
+                        mCarUxRestrictions = getDefaultRestrictions();
+                    } else {
+                        mCarUxRestrictions = carUxRestrictions;
+                    }
+
+                    for (OnUxRestrictionsChangedListener observer : mObservers) {
+                        observer.onRestrictionsChanged(mCarUxRestrictions);
+                    }
+                };
+
+        try {
+            Car.createCar(context, null,
+                    Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                    (Car car, boolean ready) -> {
+                        if (ready) {
+                            CarUxRestrictionsManager carUxRestrictionsManager =
+                                    (CarUxRestrictionsManager) car.getCarManager(
+                                            Car.CAR_UX_RESTRICTION_SERVICE);
+                            carUxRestrictionsManager.registerListener(listener);
+                            listener.onUxRestrictionsChanged(
+                                    carUxRestrictionsManager.getCurrentCarUxRestrictions());
+                        } else {
+                            Log.w(TAG, "Car service disconnected, assuming fully restricted uxr");
+                            listener.onUxRestrictionsChanged(null);
+                        }
+                    });
+
+        } catch (SecurityException e) {
+            Log.w(TAG, "Unable to connect to car service, assuming unrestricted", e);
+            listener.onUxRestrictionsChanged(new CarUxRestrictions.Builder(
+                    false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE, 0)
+                    .build());
+        }
+    }
+
+    @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. Note that this class will only hold a weak reference to the listener, you must
+     * maintain a strong reference to it elsewhere.
+     */
+    public void register(OnUxRestrictionsChangedListener listener) {
+        mObservers.add(listener);
+        listener.onRestrictionsChanged(mCarUxRestrictions);
+    }
+
+    /**
+     * Unregisters a registered listener
+     */
+    public void unregister(OnUxRestrictionsChangedListener listener) {
+        mObservers.remove(listener);
+    }
+}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateButton.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateButton.java
similarity index 93%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateButton.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateButton.java
index 61d3cab..de4888e 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateButton.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateButton.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.Button;
@@ -25,6 +26,7 @@
  * A {@link Button} that implements {@link DrawableStateView}, for allowing additional states
  * such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateButton extends Button implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateConstraintLayout.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateConstraintLayout.java
similarity index 97%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateConstraintLayout.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateConstraintLayout.java
index ec01766..861a187 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateConstraintLayout.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateConstraintLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateFrameLayout.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateFrameLayout.java
similarity index 97%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateFrameLayout.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateFrameLayout.java
index 9586640..21e040c 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateFrameLayout.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateFrameLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateImageView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateImageView.java
similarity index 93%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateImageView.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateImageView.java
index d7eb2f3..262b1e8 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateImageView.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateImageView.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.ImageView;
@@ -25,6 +26,7 @@
  * A {@link ImageView} that implements {@link DrawableStateView}, for allowing additional states
  * such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateImageView extends ImageView implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateLinearLayout.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateLinearLayout.java
similarity index 97%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateLinearLayout.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateLinearLayout.java
index 02edf73..9fb5121 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateLinearLayout.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateLinearLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateRelativeLayout.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateRelativeLayout.java
similarity index 97%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateRelativeLayout.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateRelativeLayout.java
index 5f03bb5..e1952e0 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateRelativeLayout.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateRelativeLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateSwitch.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateSwitch.java
similarity index 97%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateSwitch.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateSwitch.java
index 120f46e..312c4d7 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateSwitch.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateSwitch.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateTextView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateTextView.java
similarity index 93%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateTextView.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateTextView.java
index 78068d7..bc75abd 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateTextView.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateTextView.java
@@ -13,8 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.TextView;
@@ -25,6 +26,7 @@
  * A {@link TextView} that implements {@link DrawableStateView}, for allowing additional
  * states such as ux restriction.
  */
+@SuppressLint("AppCompatCustomView")
 public class DrawableStateTextView extends TextView implements DrawableStateView {
     private DrawableStateUtil mUtil;
 
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateUtil.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateUtil.java
similarity index 98%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateUtil.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateUtil.java
index 296d322..43b735c 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateUtil.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateUtil.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import android.view.View;
 
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateView.java b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateView.java
similarity index 96%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateView.java
rename to car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateView.java
index 7995bca..34ca1ad 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/uxr/DrawableStateView.java
+++ b/car-ui-lib/referencedesign/plugin/src/main/java/com/chassis/car/ui/plugin/uxr/DrawableStateView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.chassis.car.ui.sharedlibrary.uxr;
+package com.chassis.car.ui.plugin.uxr;
 
 import androidx.annotation.Nullable;
 
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/list_item_divider.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/list_item_divider.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/list_item_divider.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/list_item_divider.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_hint.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_hint.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_hint.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_hint.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_primary.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_primary.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_primary.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_primary.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_secondary.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_secondary.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/text_color_secondary.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/text_color_secondary.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_menu_item_icon_background_color.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_menu_item_icon_background_color.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_menu_item_icon_background_color.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_menu_item_icon_background_color.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_menu_item_icon_color.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_menu_item_icon_color.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_menu_item_icon_color.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_menu_item_icon_color.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_tab_item_selector.xml b/car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_tab_item_selector.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/color/toolbar_tab_item_selector.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/color/toolbar_tab_item_selector.xml
diff --git a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_button_ripple_background.xml
similarity index 80%
copy from car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_button_ripple_background.xml
index ec6318a..3386c53 100644
--- a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_button_ripple_background.xml
@@ -15,9 +15,6 @@
   ~ limitations under the License.
   -->
 
-<shape
+<ripple
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="@color/car_ui_scrollbar_thumb" />
-    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
-</shape>
+    android:color="#27ffffff" />
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_down.xml
similarity index 65%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_down.xml
index 6a35b43..380bf46 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_down.xml
@@ -15,10 +15,12 @@
   ~ 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>
+<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-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_up.xml
similarity index 66%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_up.xml
index 6a35b43..2eff62f 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_ic_up.xml
@@ -15,10 +15,12 @@
   ~ 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>
+<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-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_scrollbar_thumb.xml
similarity index 85%
rename from car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_scrollbar_thumb.xml
index ec6318a..65c03d9 100644
--- a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable-ldrtl/recyclerview_scrollbar_thumb.xml
@@ -18,6 +18,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-    <solid android:color="@color/car_ui_scrollbar_thumb" />
-    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
+    <solid android:color="#99ffffff" />
+    <corners android:radius="100dp"/>
 </shape>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/app_styled_view_background.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/app_styled_view_background.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/app_styled_view_background.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/app_styled_view_background.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_back.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_back.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_back.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_back.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_chevron.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_chevron.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_chevron.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_chevron.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_close.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_close.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_close.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_close.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_down.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_down.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_down.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_down.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_search.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_search.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/icon_search.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/icon_search.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_avatar_icon_outline.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_avatar_icon_outline.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_avatar_icon_outline.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_avatar_icon_outline.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_background.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_background.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_background.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_background.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_divider.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_divider.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/list_item_divider.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/list_item_divider.xml
diff --git a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_button_ripple_background.xml
similarity index 74%
copy from car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_button_ripple_background.xml
index ec6318a..50369d9 100644
--- a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_button_ripple_background.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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.
@@ -15,9 +15,6 @@
   ~ limitations under the License.
   -->
 
-<shape
+<ripple
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="@color/car_ui_scrollbar_thumb" />
-    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
-</shape>
+    android:color="#27ffffff" />
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_down.xml
similarity index 60%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_down.xml
index 6a35b43..9a242e6 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_down.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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.
@@ -15,10 +15,12 @@
   ~ 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>
+<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-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_up.xml
similarity index 61%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_up.xml
index 6a35b43..09111a9 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_ic_up.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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.
@@ -15,10 +15,12 @@
   ~ 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>
+<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-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_scrollbar_thumb.xml
similarity index 79%
copy from car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_scrollbar_thumb.xml
index ec6318a..9efbb5e 100644
--- a/car-ui-lib/referencedesign/res/drawable-ldrtl/car_ui_recyclerview_scrollbar_thumb.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/recyclerview_scrollbar_thumb.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -18,6 +18,6 @@
 <shape
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
-    <solid android:color="@color/car_ui_scrollbar_thumb" />
-    <corners android:radius="@dimen/car_ui_scrollbar_thumb_radius"/>
+    <solid android:color="#99ffffff" />
+    <corners android:radius="100dp"/>
 </shape>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_divider.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_divider.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_divider.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_divider.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_icon_background.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_icon_background.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_icon_background.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_icon_background.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_icon_ripple.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_icon_ripple.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_icon_ripple.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_icon_ripple.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_overflow.xml b/car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_overflow.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/drawable/toolbar_menu_item_overflow.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/drawable/toolbar_menu_item_overflow.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml
similarity index 89%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml
index cb4133c..4bb044f 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl-port/base_layout_toolbar.xml
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/base_layout_content_container"
@@ -14,8 +15,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="#E0000000"
-        android:tag="shared_lib_top_inset">
-        <com.chassis.car.ui.sharedlibrary.toolbar.ClickBlockingView
+        android:tag="plugin_top_inset">
+        <com.chassis.car.ui.plugin.toolbar.ClickBlockingView
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintLeft_toLeftOf="parent"
@@ -41,6 +42,8 @@
             app:layout_constraintLeft_toLeftOf="parent"
             app:layout_constraintTop_toTopOf="parent">
 
+            <!-- we need to use android:tint instead of app:tint, because we're
+                not using classes from androidx/appcompat such as AppCompatImageView -->
             <ImageView
                 android:id="@+id/toolbar_nav_icon"
                 android:tint="@color/toolbar_menu_item_icon_color"
@@ -49,7 +52,8 @@
                 android:layout_width="44dp"
                 android:layout_height="44dp"
                 android:layout_gravity="center"
-                android:scaleType="fitXY"/>
+                android:scaleType="fitXY"
+                tools:ignore="UseAppTint"/>
 
             <ImageView
                 android:id="@+id/toolbar_logo"
@@ -97,7 +101,7 @@
                 android:textAppearance="?android:attr/textAppearanceSmall"/>
         </LinearLayout>
 
-        <com.chassis.car.ui.sharedlibrary.toolbar.TabLayout
+        <com.chassis.car.ui.plugin.toolbar.TabLayout
             android:id="@+id/toolbar_tabs"
             android:layout_width="0dp"
             android:layout_height="@dimen/toolbar_row_height"
@@ -143,4 +147,9 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
+    <ViewStub
+        android:id="@+id/toolbar_dialog_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/toolbar_dialog"/>
 </FrameLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl/base_layout_toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl/base_layout_toolbar.xml
similarity index 92%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl/base_layout_toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl/base_layout_toolbar.xml
index debe09b..74f39d8 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-ldrtl/base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout-ldrtl/base_layout_toolbar.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     xmlns:app="http://schemas.android.com/apk/res-auto">
@@ -14,8 +15,8 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/toolbar_row_height"
         android:background="#E0000000"
-        android:tag="shared_lib_top_inset">
-        <com.chassis.car.ui.sharedlibrary.toolbar.ClickBlockingView
+        android:tag="plugin_top_inset">
+        <com.chassis.car.ui.plugin.toolbar.ClickBlockingView
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintLeft_toLeftOf="parent"
@@ -42,7 +43,8 @@
                 android:layout_width="44dp"
                 android:layout_height="44dp"
                 android:layout_gravity="center"
-                android:scaleType="fitXY"/>
+                android:scaleType="fitXY"
+                tools:ignore="UseAppTint" />
 
             <ImageView
                 android:id="@+id/toolbar_logo"
@@ -90,7 +92,7 @@
                 android:textAppearance="?android:attr/textAppearanceSmall"/>
         </LinearLayout>
 
-        <com.chassis.car.ui.sharedlibrary.toolbar.TabLayout
+        <com.chassis.car.ui.plugin.toolbar.TabLayout
             android:id="@+id/toolbar_tabs"
             android:layout_width="wrap_content"
             android:layout_height="0dp"
@@ -136,4 +138,9 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
+    <ViewStub
+        android:id="@+id/toolbar_dialog_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/toolbar_dialog"/>
 </FrameLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/base_layout_toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout-port/base_layout_toolbar.xml
similarity index 92%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/base_layout_toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout-port/base_layout_toolbar.xml
index 654e502..5daf874 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout-port/base_layout_toolbar.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     xmlns:app="http://schemas.android.com/apk/res-auto">
@@ -14,8 +15,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="#E0000000"
-        android:tag="shared_lib_top_inset">
-        <com.chassis.car.ui.sharedlibrary.toolbar.ClickBlockingView
+        android:tag="plugin_top_inset">
+        <com.chassis.car.ui.plugin.toolbar.ClickBlockingView
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintStart_toStartOf="parent"
@@ -49,7 +50,8 @@
                 android:layout_width="44dp"
                 android:layout_height="44dp"
                 android:layout_gravity="center"
-                android:scaleType="fitXY"/>
+                android:scaleType="fitXY"
+                tools:ignore="UseAppTint" />
 
             <ImageView
                 android:id="@+id/toolbar_logo"
@@ -93,7 +95,7 @@
                 android:textAppearance="?android:attr/textAppearanceSmall"/>
         </LinearLayout>
 
-        <com.chassis.car.ui.sharedlibrary.toolbar.TabLayout
+        <com.chassis.car.ui.plugin.toolbar.TabLayout
             android:id="@+id/toolbar_tabs"
             android:layout_width="match_parent"
             android:layout_height="@dimen/toolbar_row_height"
@@ -135,4 +137,10 @@
             app:layout_constraintStart_toStartOf="parent"/>
 
     </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <ViewStub
+        android:id="@+id/toolbar_dialog_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/toolbar_dialog"/>
 </FrameLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout-port/toolbar_tab.xml
similarity index 82%
copy from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/layout-port/toolbar_tab.xml
index 4b70766..4f63eac 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout-port/toolbar_tab.xml
@@ -2,13 +2,16 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="wrap_content"
+    android:layout_width="0dp"
     android:layout_height="match_parent"
+    android:layout_weight="1"
     android:orientation="vertical"
     android:paddingStart="12dp"
     android:paddingEnd="12dp"
     android:gravity="center"
     android:background="?android:attr/selectableItemBackground">
+    <!-- we need to use android:tint instead of app:tint, because we're
+        not using classes from androidx/appcompat such as AppCompatImageView -->
     <ImageView
         android:id="@+id/car_ui_toolbar_tab_item_icon"
         android:layout_width="36dp"
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/app_styled_view.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/app_styled_view.xml
similarity index 94%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/app_styled_view.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/app_styled_view.xml
index f2cf94b..feb0984 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/app_styled_view.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/app_styled_view.xml
@@ -28,8 +28,9 @@
       android:layout_height="44dp"
       android:src="@drawable/icon_close"
       android:background="@drawable/toolbar_menu_item_icon_ripple"
+      android:visibility="invisible"
       app:layout_constraintStart_toStartOf="parent"
-      />
+      app:layout_constraintTop_toTopOf="parent"/>
 
   <ScrollView
       android:id="@+id/app_styled_content"
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/base_layout.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/base_layout.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/base_layout.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/base_layout.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/base_layout_toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/base_layout_toolbar.xml
similarity index 90%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/base_layout_toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/base_layout_toolbar.xml
index 8baf991..4108bc1 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/base_layout_toolbar.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    android:layout_height="match_parent">
 
     <FrameLayout
         android:id="@+id/base_layout_content_container"
@@ -15,8 +15,8 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/toolbar_row_height"
         android:background="#E0000000"
-        android:tag="shared_lib_top_inset">
-        <com.chassis.car.ui.sharedlibrary.toolbar.ClickBlockingView
+        android:tag="plugin_top_inset">
+        <com.chassis.car.ui.plugin.toolbar.ClickBlockingView
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintStart_toStartOf="parent"
@@ -35,6 +35,8 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent">
 
+            <!-- we need to use android:tint instead of app:tint, because we're
+                not using classes from androidx/appcompat such as AppCompatImageView -->
             <ImageView
                 android:id="@+id/toolbar_nav_icon"
                 android:tint="@color/toolbar_menu_item_icon_color"
@@ -88,7 +90,7 @@
                 android:textAppearance="?android:attr/textAppearanceSmall"/>
         </LinearLayout>
 
-        <com.chassis.car.ui.sharedlibrary.toolbar.TabLayout
+        <com.chassis.car.ui.plugin.toolbar.TabLayout
             android:id="@+id/toolbar_tabs"
             android:layout_width="wrap_content"
             android:layout_height="0dp"
@@ -136,4 +138,9 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
+    <ViewStub
+        android:id="@+id/toolbar_dialog_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout="@layout/toolbar_dialog"/>
 </FrameLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/header_list_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/header_list_item.xml
similarity index 95%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/header_list_item.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/header_list_item.xml
index 2805774..03647f1 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/header_list_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/header_list_item.xml
@@ -20,7 +20,8 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:background="@android:color/transparent"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/list_item_header_height">
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/list_item_header_height">
 
     <TextView
         android:id="@+id/list_item_title"
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/list_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/list_item.xml
similarity index 98%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/list_item.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/list_item.xml
index 32499e6..159723e 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/list_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/list_item.xml
@@ -25,7 +25,7 @@
 
     <!-- The following touch interceptor views are sized to encompass the specific sub-sections of
     the list item view to easily control the bounds of a background ripple effects. -->
-    <View
+    <com.chassis.car.ui.plugin.SecureView
         android:id="@+id/list_item_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -36,7 +36,7 @@
         app:layout_constraintTop_toTopOf="parent" />
 
     <!-- This touch interceptor does not include the action container -->
-    <View
+    <com.chassis.car.ui.plugin.SecureView
         android:id="@+id/list_item_reduced_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -108,7 +108,7 @@
         app:layout_goneMarginStart="@dimen/list_item_text_no_icon_start_margin" />
 
     <!-- This touch interceptor is sized and positioned to encompass the action container   -->
-    <View
+    <com.chassis.car.ui.plugin.SecureView
         android:id="@+id/list_item_action_container_touch_interceptor"
         android:layout_width="0dp"
         android:layout_height="0dp"
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view.xml
similarity index 67%
copy from car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view.xml
index f487e35..3ec12f2 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright 2021 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.
@@ -12,22 +12,21 @@
   ~ 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
+  ~ limitations under the License.
   -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
-        android:id="@+id/car_ui_recycler_view"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingStart="@dimen/car_ui_scrollbar_margin"
-        android:paddingEnd="@dimen/car_ui_scrollbar_margin"
-        android:tag="carUiRecyclerView" />
+        android:layout_marginStart="@dimen/scrollbar_margin"
+        android:layout_marginEnd="@dimen/scrollbar_margin"
+        android:tag="RecyclerView" />
 
     <FrameLayout
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_gravity="left" >
-        <include layout="@layout/car_ui_recyclerview_scrollbar"/>
+        <include layout="@layout/recyclerview_scrollbar"/>
     </FrameLayout>
 </merge>
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_medium.xml
similarity index 67%
rename from car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_medium.xml
index f487e35..9084eef 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recycler_view.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_medium.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright 2021 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.
@@ -12,22 +12,21 @@
   ~ 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
+  ~ limitations under the License.
   -->
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <com.android.car.ui.recyclerview.CarUiRecyclerViewContainer
-        android:id="@+id/car_ui_recycler_view"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:scrollbars="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingStart="@dimen/car_ui_scrollbar_margin"
-        android:paddingEnd="@dimen/car_ui_scrollbar_margin"
-        android:tag="carUiRecyclerView" />
+        android:layout_marginLeft="@dimen/scrollbar_margin"
+        android:tag="RecyclerView" />
 
     <FrameLayout
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_gravity="left" >
-        <include layout="@layout/car_ui_recyclerview_scrollbar"/>
+        <include layout="@layout/recyclerview_scrollbar"/>
     </FrameLayout>
 </merge>
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_no_scrollbar.xml
similarity index 64%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_no_scrollbar.xml
index 6a35b43..05663d8 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_no_scrollbar.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2021 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,11 +14,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
-<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>
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:tag="RecyclerView" />
+</merge>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_small.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_small.xml
new file mode 100644
index 0000000..bc8a387
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recycler_view_small.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="left" >
+            <include layout="@layout/recyclerview_scrollbar"/>
+        </FrameLayout>
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:tag="RecyclerView" />
+    </LinearLayout>
+</merge>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/layout/recyclerview_scrollbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recyclerview_scrollbar.xml
new file mode 100644
index 0000000..e9c9efc
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/recyclerview_scrollbar.xml
@@ -0,0 +1,81 @@
+<?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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="@dimen/scrollbar_container_width"
+    android:layout_height="match_parent"
+    android:id="@+id/scroll_bar"
+    android:layout_gravity="left"
+    android:gravity="center"
+    tools:ignore="MissingDefaultResource">
+
+    <ImageView
+        android:id="@+id/scrollbar_page_up"
+        android:layout_width="@dimen/scrollbar_button_size"
+        android:layout_height="@dimen/scrollbar_button_size"
+        android:background="@drawable/recyclerview_button_ripple_background"
+        android:contentDescription="@string/scrollbar_page_up_button"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:src="@drawable/recyclerview_ic_up"
+        android:scaleType="centerInside"
+        android:layout_marginTop="15dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"/>
+
+    <!-- View height is dynamically calculated during layout. -->
+    <View
+        android:id="@+id/scrollbar_thumb"
+        android:layout_width="@dimen/scrollbar_thumb_width"
+        android:layout_height="0dp"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/recyclerview_scrollbar_thumb"
+        app:layout_constraintTop_toTopOf="@+id/scrollbar_track"
+        app:layout_constraintBottom_toBottomOf="@+id/scrollbar_track"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"/>
+
+    <View
+        android:id="@+id/scrollbar_track"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/scrollbar_separator_margin"
+        android:layout_marginBottom="@dimen/scrollbar_separator_margin"
+        app:layout_constraintTop_toBottomOf="@+id/scrollbar_page_up"
+        app:layout_constraintBottom_toTopOf="@+id/scrollbar_page_down"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <ImageView
+        android:id="@+id/scrollbar_page_down"
+        android:layout_width="@dimen/scrollbar_button_size"
+        android:layout_height="@dimen/scrollbar_button_size"
+        android:background="@drawable/recyclerview_button_ripple_background"
+        android:contentDescription="@string/scrollbar_page_down_button"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:src="@drawable/recyclerview_ic_down"
+        android:scaleType="centerInside"
+        android:layout_marginBottom="15dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_dialog.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_dialog.xml
new file mode 100644
index 0000000..e88f146
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_dialog.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:alpha="0">
+
+    <View
+        android:id="@+id/toolbar_dialog_shade"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#99000000"/>
+
+    <!-- This ConstraintLayout is the grey box of the dialog.
+         It's clickable in order to block clicks before they reach the shade. -->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="750dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:minHeight="48dp"
+        android:background="#ff282a2d"
+        android:orientation="vertical"
+        android:clickable="true">
+
+        <ScrollView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/toolbar_dialog_dismiss_button"
+            app:layout_constraintHeight_max="400dp"
+            app:layout_constrainedHeight="true">
+            <LinearLayout
+                android:id="@+id/toolbar_dialog_linear_layout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"/>
+        </ScrollView>
+
+        <Button
+            android:id="@+id/toolbar_dialog_dismiss_button"
+            android:layout_width="wrap_content"
+            android:layout_height="76dp"
+            android:minWidth="79dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="16dp"
+            android:text="@string/dialog_dismiss_button"
+            android:textAllCaps="false"
+            android:textSize="26sp"
+            style="@android:style/Widget.Material.Button.Borderless.Colored"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintLeft_toLeftOf="parent"/>
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item.xml
similarity index 89%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item.xml
index c2d3609..3b49390 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item.xml
@@ -25,13 +25,13 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center">
-        <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateImageView
+        <com.chassis.car.ui.plugin.uxr.DrawableStateImageView
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:src="@drawable/toolbar_menu_item_icon_background"
             android:background="@drawable/toolbar_menu_item_icon_ripple"
             android:scaleType="center"/>
-        <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateImageView
+        <com.chassis.car.ui.plugin.uxr.DrawableStateImageView
             android:id="@+id/car_ui_toolbar_menu_item_icon"
             android:layout_width="@dimen/primary_icon_size"
             android:layout_height="@dimen/primary_icon_size"
@@ -39,7 +39,7 @@
             android:tint="@color/toolbar_menu_item_icon_color"
             android:tintMode="src_in"/>
     </FrameLayout>
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateSwitch
+    <com.chassis.car.ui.plugin.uxr.DrawableStateSwitch
         android:id="@+id/car_ui_toolbar_menu_item_switch"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -47,14 +47,14 @@
         android:clickable="false"/>
 
     <!-- These buttons must have clickable="false" or they will steal the click events from the container -->
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateButton
+    <com.chassis.car.ui.plugin.uxr.DrawableStateButton
         android:id="@+id/car_ui_toolbar_menu_item_text"
         style="@style/Toolbar.MenuItem.Text.Borderless"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:clickable="false"/>
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateButton
+    <com.chassis.car.ui.plugin.uxr.DrawableStateButton
         android:id="@+id/car_ui_toolbar_menu_item_text_with_icon"
         style="@style/Toolbar.MenuItem.Text.Borderless"
         android:drawableTint="@color/toolbar_menu_item_icon_color"
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item_primary.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item_primary.xml
similarity index 89%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item_primary.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item_primary.xml
index df0e783..89ba1b1 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_menu_item_primary.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_menu_item_primary.xml
@@ -25,13 +25,13 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center">
-        <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateImageView
+        <com.chassis.car.ui.plugin.uxr.DrawableStateImageView
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:src="@drawable/toolbar_menu_item_icon_background"
             android:background="@drawable/toolbar_menu_item_icon_ripple"
             android:scaleType="center"/>
-        <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateImageView
+        <com.chassis.car.ui.plugin.uxr.DrawableStateImageView
             android:id="@+id/car_ui_toolbar_menu_item_icon"
             android:layout_width="@dimen/primary_icon_size"
             android:layout_height="@dimen/primary_icon_size"
@@ -39,7 +39,7 @@
             android:tint="@color/toolbar_menu_item_icon_color"
             android:tintMode="src_in"/>
     </FrameLayout>
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateSwitch
+    <com.chassis.car.ui.plugin.uxr.DrawableStateSwitch
         android:id="@+id/car_ui_toolbar_menu_item_switch"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -47,14 +47,14 @@
         android:clickable="false"/>
 
     <!-- These buttons must have clickable="false" or they will steal the click events from the container -->
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateButton
+    <com.chassis.car.ui.plugin.uxr.DrawableStateButton
         android:id="@+id/car_ui_toolbar_menu_item_text"
         style="@style/Toolbar.MenuItem.Text"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:clickable="false"/>
-    <com.chassis.car.ui.sharedlibrary.uxr.DrawableStateButton
+    <com.chassis.car.ui.plugin.uxr.DrawableStateButton
         android:id="@+id/car_ui_toolbar_menu_item_text_with_icon"
         style="@style/Toolbar.MenuItem.Text"
         android:drawableTint="@color/toolbar_menu_item_icon_color"
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_overflow_item.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_overflow_item.xml
new file mode 100644
index 0000000..940d2c4
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_overflow_item.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="116dp"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    android:background="?android:attr/selectableItemBackground">
+
+    <TextView
+        android:id="@+id/toolbar_overflow_item_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="24dp"
+        android:paddingEnd="24dp"
+        android:textAlignment="viewStart"
+        android:textSize="32sp"
+        android:textAppearance="@android:style/TextAppearance.Material.Body1"/>
+</LinearLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_search_view.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_search_view.xml
similarity index 96%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_search_view.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_search_view.xml
index c2ab9f5..b51fe46 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_search_view.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_search_view.xml
@@ -9,7 +9,7 @@
     app:layout_constraintStart_toEndOf="@+id/toolbar_nav_icon_container"
     app:layout_constraintTop_toTopOf="parent">
 
-  <com.chassis.car.ui.sharedlibrary.toolbar.OnPrivateImeCommandEditText
+  <com.chassis.car.ui.plugin.toolbar.OnPrivateImeCommandEditText
       android:id="@+id/toolbar_search_bar"
       android:layout_height="match_parent"
       android:layout_width="match_parent"
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_tab.xml
similarity index 87%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_tab.xml
index 4b70766..26cc684 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout/toolbar_tab.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/layout/toolbar_tab.xml
@@ -9,6 +9,8 @@
     android:paddingEnd="12dp"
     android:gravity="center"
     android:background="?android:attr/selectableItemBackground">
+    <!-- we need to use android:tint instead of app:tint, because we're
+        not using classes from androidx/appcompat such as AppCompatImageView -->
     <ImageView
         android:id="@+id/car_ui_toolbar_tab_item_icon"
         android:layout_width="36dp"
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-af/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..76a371c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-af/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Maak toe"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Soek …"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nie beskikbaar terwyl jy bestuur nie"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Terug"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Oorloop"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Rollees af"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Rollees op"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-am/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..6ba766b
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-am/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ዝጋ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ይፈልጉ…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"እየነዱ ሳለ አይገኝም"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ተመለስ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ትርፍ ፍሰት"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ወደ ታች ይሸብልሉ"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ወደ ላይ ይሸብልሉ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ar/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..4bc6549
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ar/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"إغلاق"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"بحث…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"غير متاح أثناء القيادة"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"رجوع"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"القائمة الكاملة"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"الانتقال للأسفل"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"الانتقال للأعلى"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-as/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..a96f8ea
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-as/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"বন্ধ কৰক"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"সন্ধান কৰক…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"গাড়ী চলাই থকা অৱস্থাত উপলব্ধ নহয়"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"উভতি যাওক"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"অভাৰফ্ল’"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"তললৈ স্ক্ৰ’ল কৰক"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ওপৰলৈ স্ক্ৰ’ল কৰক"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-az/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..4831637
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-az/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Bağlayın"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Axtarış…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Avtomobil sürərkən əlçatan deyil"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Geri"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Kənara çıxma"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Aşağı sürüşdürün"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Yuxarı sürüşdürün"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-b+sr+Latn/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..4b894d9
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zatvori"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Pretražite…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nedostupno tokom vožnje"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Nazad"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Preklopni meni"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Skrolujte nadole"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Skrolujte nagore"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-be/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..54c40ee
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-be/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Закрыць"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Пошук…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Падчас руху аўтамабіля пошук недаступны"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Дадатковае меню"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Прагартаць уніз"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Прагартаць уверх"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-bg/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..b27e6e1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-bg/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Затваряне"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Търсете…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Не е налице по време на шофиране"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Препълване"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Превъртане надолу"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Превъртане нагоре"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-bn/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..d0185fb
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-bn/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"বন্ধ করুন"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"সার্চ করুন…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ড্রাইভ করার সময় সার্চ করা যাবে না"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ফিরুন"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ওভারফ্লো"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"নিচের দিকে স্ক্রল করুন"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"উপরের দিকে স্ক্রল করুন"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-bs/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..59cb803
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-bs/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zatvori"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Pretražite…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nije dostupno tokom vožnje"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Nazad"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Preklopni meni"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Klizanje nadolje"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Klizanje nagore"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ca/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..0de3642
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ca/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Tanca"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Cerca…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"No està disponible mentre condueixes"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Enrere"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menú addicional"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Desplaça cap avall"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Desplaça cap amunt"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-cs/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..76b73e5
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-cs/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zavřít"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Vyhledat…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Při řízení nedostupné"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Zpět"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Rozbalovací nabídka"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Posunout dolů"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Posunout nahoru"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-da/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..6c10188
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-da/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Luk"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Søg…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ikke tilgængelig under kørsel"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Tilbage"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Prikmenu"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Rul ned"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Rul op"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-de/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..2b55694
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-de/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Schließen"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Suchen…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Während der Fahrt nicht verfügbar"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Zurück"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Weitere Optionen"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Nach unten scrollen"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Nach oben scrollen"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-el/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..ef20ec3
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-el/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Κλείσιμο"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Αναζήτηση…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Μη διαθέσιμο κατά την οδήγηση"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Πίσω"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Υπερχείλιση"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Κύλιση προς τα κάτω"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Κύλιση προς τα επάνω"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rAU/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..4fbc0e1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Close"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Search…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Unavailable while driving"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Back"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflow"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scroll down"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scroll up"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rCA/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..4fbc0e1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Close"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Search…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Unavailable while driving"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Back"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflow"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scroll down"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scroll up"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rGB/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..4fbc0e1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Close"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Search…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Unavailable while driving"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Back"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflow"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scroll down"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scroll up"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rIN/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..4fbc0e1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Close"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Search…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Unavailable while driving"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Back"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflow"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scroll down"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scroll up"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rXC/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..8bdfb64
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎Close‎‏‎‎‏‎"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‎Search…‎‏‎‎‏‎"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‎Unavailable while driving‎‏‎‎‏‎"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎Back‎‏‎‎‏‎"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‎Overflow‎‏‎‎‏‎"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎Scroll down‎‏‎‎‏‎"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎Scroll up‎‏‎‎‏‎"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-es-rUS/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..217451d
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Cerrar"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Buscar…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"No disponible mientras conduces"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atrás"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Ampliado"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Desplazar hacia abajo"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Desplazar hacia arriba"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-es/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..5d3da36
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-es/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Cerrar"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Buscar…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"No disponible mientras conduces"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atrás"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menú adicional"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Desplazarse hacia abajo"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Desplazarse hacia arriba"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-et/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..aa89760
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-et/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Sule"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Otsing …"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Pole sõitmise ajal saadaval"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Tagasi"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Ületäide"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Alla kerimine"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Üles kerimine"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-eu/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..fcd259c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-eu/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Itxi"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Bilatu…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ezin da erabili gidatu bitartean"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atzera"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Luzapena"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Egin behera"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Egin gora"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-fa/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..cc16618
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-fa/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"بستن"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"جستجو…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"هنگام رانندگی دردسترس نیست"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"برگشت"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"لبریزشده"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"پیمایش به‌پایین"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"پیمایش به‌بالا"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-fi/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..13f0580
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-fi/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Sulje"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Hae…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ei käytettävissä ajon aikana"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Takaisin"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Ylivuoto"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Vieritä alas"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Vieritä ylös"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-fr-rCA/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..67738e9
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Fermer"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Recherche en cours…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Indisponible pendant que vous conduisez"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Retour"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menu déroulant"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Faire défiler vers le bas"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Faire défiler vers le haut"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-fr/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..0c68f4c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-fr/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Fermer"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Rechercher…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Non disponible lorsque vous conduisez"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Retour"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menu à développer"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Faire défiler vers le bas"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Faire défiler vers le haut"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-gl/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..5c6fa5d
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-gl/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Pechar"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Busca…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Non está dispoñible mentres conduces"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atrás"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menú adicional"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Desprazarse abaixo"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Desprazarse arriba"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-gu/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..386f67e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-gu/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"બંધ કરો"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"શોધો…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ડ્રાઇવ કરતી વખતે આ સુવિધા ઉપલબ્ધ નથી"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"પાછળ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ઓવરફ્લો"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"નીચે સ્ક્રોલ કરો"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ઉપર સ્ક્રોલ કરો"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-hi/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..8af5540
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-hi/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"बंद करें"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"खोजें…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"गाड़ी चलाते समय यह सुविधा उपलब्ध नहीं है"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"वापस जाएं"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ओवरफ़्लो मेन्यू"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"नीचे की ओर स्क्रोल करें"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ऊपर की ओर स्क्रोल करें"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-hr/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..46fd0b4
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-hr/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zatvori"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Pretražite…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nije dostupno tijekom vožnje"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Natrag"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Dodatno"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Pomicanje prema dolje"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Pomicanje prema gore"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-hu/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..e498487
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-hu/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Bezár"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Keresés…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Vezetés közben nem áll rendelkezésre"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Vissza"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"További elemek"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Görgetés lefelé"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Görgetés felfelé"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-hy/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..7bddec2
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-hy/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Փակել"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Որոնում…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Հասանելի չէ վարելու ընթացքում"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Հետ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Լրացուցիչ ընտրացանկ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Ոլորել վար"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Ոլորել վեր"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-in/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..269ac97
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-in/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Tutup"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Telusuri…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Tidak tersedia saat mengemudi"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Kembali"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Tambahan"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scroll ke bawah"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scroll ke atas"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-is/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..aca0863
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-is/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Loka"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Leita…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ekki í boði við akstur"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Til baka"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Yfirflæði"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Fletta niður"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Fletta upp"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-it/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..68a1f04
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-it/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Chiudi"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Cerca…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Non disponibile durante la guida"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Indietro"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Extra"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scorri verso il basso"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scorri verso l\'alto"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-iw/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..49d0737
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-iw/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"סגירה"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"חיפוש…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"לא זמין במהלך הנהיגה"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"חזרה"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"אפשרויות נוספות"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"גלילה למטה"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"גלילה למעלה"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ja/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..e0bb3f4
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ja/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"閉じる"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"検索…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"運転中は使用できません"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"戻る"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"オーバーフロー"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"下にスクロール"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"上にスクロール"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ka/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..ca07493
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ka/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"დახურვა"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ძიება…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"მიუწვდომელია მანქანის მართვისას"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"უკან"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"გადავსება"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ქვემოთ გადაადგილება"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ზემოთ გადაადგილება"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-kk/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..091c9b2
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-kk/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Жабу"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Іздеу…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Көлік жүргізгенде қолжетімсіз"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Артқа"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Қосымша мәзір"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Төмен айналдыру"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Жоғары айналдыру"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-km/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..9747439
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-km/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"បិទ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ស្វែងរក…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"មិនអាចប្រើបានទេ ខណៈពេល​កំពុងបើកបរ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ថយក្រោយ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ម៉ឺនុយបន្ថែម"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"រំកិលចុះក្រោម"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"រំកិល​​ឡើង​លើ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-kn/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..a7d7177
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-kn/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ಮುಚ್ಚಿರಿ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ಹುಡುಕಿ…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ಚಾಲನೆ ಮಾಡುವಾಗ ಲಭ್ಯವಿರುವುದಿಲ್ಲ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ಹಿಂದಿನದು"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ಓವರ್‌ಫ್ಲೋ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ಕೆಳಗೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ಮೇಲೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ko/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..2db02eb
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ko/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"닫기"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"검색…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"운전 중에는 사용할 수 없음"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"뒤로"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"더보기"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"아래로 스크롤"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"위로 스크롤"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ky/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..0ad51e2
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ky/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Жабуу"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Издөө…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Айдап баратканда жеткиликсиз"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Артка"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Кошумча меню"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Төмөн сыдыруу"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Жогору сыдыруу"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values-ldrtl/values-toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ldrtl/values-toolbar.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values-ldrtl/values-toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values-ldrtl/values-toolbar.xml
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-lo/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..484f5cb
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-lo/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ປິດ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ຊອກຫາ…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ບໍ່ສາມາດນຳໃຊ້ໄດ້ໃນຂະນະຂັບຂີ່"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ກັບຄືນ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ລາຍການເພີ່ມເຕີມ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ເລື່ອນລົງ"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ເລື່ອນຂຶ້ນ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-lt/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..9a41e8f
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-lt/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Uždaryti"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Ieškoti…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nepasiekiama vairuojant"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atgal"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Perpildymas"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Slinkti žemyn"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Slinkti aukštyn"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-lv/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..e8da375
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-lv/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Aizvērt"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Meklēt…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Šī funkcija nav pieejama braukšanas laikā"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Atpakaļ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Pārpilde"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Ritināt lejup"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Ritināt augšup"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-mk/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..c6bc34e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-mk/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Затвори"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Пребарувајте…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Недостапно при возење"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Проширено мени"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Оди надолу"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Оди нагоре"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ml/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..9f975e5
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ml/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"അടയ്ക്കുക"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"തിരയുക…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ഡ്രെെവ് ചെയ്യുമ്പോൾ ലഭ്യമല്ല"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"മടങ്ങുക"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ഓവർഫ്ലോ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"താഴേക്ക് സ്ക്രോൾ ചെയ്യുക"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"മുകളിലേക്ക് സ്ക്രോൾ ചെയ്യുക"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-mn/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..7902290
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-mn/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Хаах"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Хайх…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Жолоо барьж байх үед боломжгүй"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Буцах"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Дэлгэдэг цэс"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Доош гүйлгэх"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Дээш гүйлгэх"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-mr/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..6dc8852
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-mr/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"बंद करा"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"शोधा…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ड्राइव्ह करत असताना उपलब्ध नाही"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"मागे जा"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ओव्हरफ्लो"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"खाली स्क्रोल करा"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"वर स्क्रोल करा"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ms/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..a47e31b
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ms/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Tutup"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Cari…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Tidak tersedia semasa memandu"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Kembali"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Limpahan"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Tatal ke bawah"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Tatal ke atas"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-my/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..bfa798a
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-my/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ပိတ်ရန်"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ရှာဖွေရန်…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ကားမောင်းနေစဉ် မရနိုင်ပါ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"နောက်သို့"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"မီနူးအပို"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"အောက်သို့ လှိမ့်ရန်"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"အပေါ်သို့ လှိမ့်ရန်"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-nb/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..7fde85c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-nb/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Lukk"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Søk"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ikke tilgjengelig mens du kjører"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Tilbake"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflyt"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Rull ned"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Rull opp"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ne/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..8a711cf
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ne/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"बन्द गर्नुहोस्"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"खोज्नुहोस्…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"सवारी साधन चलाइरहेका बेला यो सुविधा उपलब्ध हुँदैन"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"पछाडि"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ओभरफ्लो"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"तलतिर स्क्रोल गर्नुहोस्"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"माथितिर स्क्रोल गर्नुहोस्"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-nl/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..f5691ae
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-nl/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Sluiten"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Zoeken…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Niet beschikbaar tijdens het rijden"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Terug"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overloop"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Omlaag scrollen"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Omhoog scrollen"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-or/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..28f228b
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-or/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ସନ୍ଧାନ କରନ୍ତୁ…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ଡ୍ରାଇଭିଂ ସମୟରେ ଅନୁପଲବ୍ଧ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ଓଭରଫ୍ଲୋ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ତଳକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ଉପରକୁ ସ୍କ୍ରୋଲ୍ କରନ୍ତୁ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-pa/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..7c5a7e3
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-pa/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ਬੰਦ ਕਰੋ"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ਖੋਜੋ…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ਗੱਡੀ ਚਲਾਉਣ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ਪਿੱਛੇ ਜਾਓ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ਓਵਰਫ਼ਲੋ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"ਹੇਠਾਂ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ਉੱਪਰ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-pl/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..d718b62
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-pl/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zamknij"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Szukaj…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Niedostępne podczas jazdy"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Wstecz"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Przycisk menu"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Przewiń w dół"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Przewiń w górę"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values-port/values-toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-port/values-toolbar.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values-port/values-toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values-port/values-toolbar.xml
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-pt-rPT/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..a660eb3
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Fechar"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Pesquise…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Indisponível durante a condução"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Anterior"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Adicional"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Deslocar para baixo"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Deslocar para cima"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-pt/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..5c9edb3
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-pt/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Fechar"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Pesquisar…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Indisponível ao dirigir"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Voltar"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menu flutuante"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Rolar para baixo"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Rolar para cima"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ro/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..fbd338a
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ro/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Închideți"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Căutați…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Indisponibil când sunteți la volan"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Înapoi"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Suplimentar"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Derulați în jos"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Derulați în sus"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ru/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..88ae531
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ru/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Закрыть"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Введите запрос"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Недоступно во время вождения"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Дополнительное меню"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Прокрутить вниз"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Прокрутить вверх"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-si/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..0c6079f
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-si/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"වසන්න"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"සොයන්න…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"රිය පදවන අතරතුර ලබා ගත නොහැකිය"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"ආපසු"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"පිටාර යාම"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"පහළට අනුචලනය කරන්න"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"ඉහළට අනුචලනය කරන්න"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sk/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..5fc689d
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sk/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zavrieť"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Vyhľadať…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nie je k dispozícii počas jazdy"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Späť"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Rozšírená ponuka"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Posunúť nadol"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Posunúť nahor"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sl/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..0ab7a2a
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sl/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Zapri"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Iskanje …"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ni na voljo med vožnjo"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Nazaj"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Gumb menija z dodatnimi elementi"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Pomik navzdol"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Pomik navzgor"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sq/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..6a1bcb1
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sq/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Mbyll"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Kërko…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Nuk ofrohet gjatë drejtimit të makinës"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Prapa"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Menyja e tejkalimit"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Lëviz poshtë"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Lëviz lart"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sr/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..4cc060e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sr/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Затвори"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Претражите…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Недоступно током вожње"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Преклопни мени"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Скролујте надоле"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Скролујте нагоре"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sv/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..a56b694
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sv/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Stäng"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Sök …"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Inte tillgängligt under körning"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Tillbaka"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Fler alternativ"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Scrolla nedåt"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Scrolla uppåt"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-sw/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..e287799
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-sw/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Funga"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Tafuta…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Haipatikani unapoendesha gari"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Nyuma"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Vipengee vya ziada"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Sogeza chini"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Sogeza juu"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ta/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..e4c9c4c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ta/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"மூடுக"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"தேடுக…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"வாகனம் ஓட்டும்போது கிடைக்காது"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"பின்செல்லும்"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"கூடுதல் விருப்பங்கள்"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"கீழே செல்லும்"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"மேலே செல்லும்"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-te/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..f204e2f
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-te/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"మూసివేయండి"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"వెతకండి…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"డ్రైవింగ్‌లో ఉన్నప్పుడు అందుబాటులో ఉండదు"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"వెనుకకు"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"ఓవర్‌ఫ్లో"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"కిందికి స్క్రోల్ చేయండి"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"పైకి స్క్రోల్ చేయండి"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-th/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..b8009e6
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-th/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"ปิด"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"ค้นหา…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ใช้งานไม่ได้ขณะขับรถ"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"กลับ"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"รายการเพิ่มเติม"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"เลื่อนลง"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"เลื่อนขึ้น"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-tl/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..008c7cb
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-tl/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Isara"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Maghanap…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Hindi available kapag nagmamaneho"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Bumalik"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Overflow"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Mag-scroll pababa"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Mag-scroll pataas"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-tr/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..3546f0d
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-tr/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Kapat"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Ara…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Sürüş sırasında kullanılamıyor"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Geri"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Taşma"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Aşağı kaydır"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Yukarı kaydır"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-uk/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..f41f4c5
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-uk/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Закрити"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Шукайте…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Недоступно під час руху автомобіля"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Назад"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Додаткове меню"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Прокрутити вниз"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Прокрутити вгору"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-ur/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..82aa62f
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-ur/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"بند کریں"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"تلاش کریں…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"ڈرائیونگ کے دوران دستیاب نہیں ہے"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"واپس جائیں"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"اوورفلو"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"نیچے اسکرول کریں"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"اوپر اسکرول کریں"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-uz/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..129f3ce
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-uz/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Yopish"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Qidirish…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Haydash vaqtida ishlamaydi"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Orqaga"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Kengaytirilgan"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Pastga tushish"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Tepaga siljit"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-vi/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..00edd25
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-vi/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Đóng"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Tìm kiếm…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Không dùng được khi bạn đang lái xe"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Quay lại"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Trình đơn mục bổ sung"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Di chuyển xuống"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Di chuyển lên"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rCN/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..9c157b4
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"关闭"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"搜索…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"车辆行驶期间不可用"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"返回"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"溢出按钮"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"向下滚动"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"向上滚动"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rHK/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..962d39c
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"關閉"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"搜尋…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"駕駛時無法使用"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"返回"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"顯示更多"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"向下捲動"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"向上捲動"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rTW/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..7192a6e
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"關閉"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"搜尋…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"開車時無法使用"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"返回"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"溢位"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"向下捲動"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"向上捲動"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values-zu/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..b63da70
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values-zu/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dialog_dismiss_button" msgid="2252701669002484829">"Vala"</string>
+    <string name="toolbar_default_search_hint" msgid="5517905201169820324">"Sesha…"</string>
+    <string name="toolbar_ux_restricted_search_hint" msgid="4107155717310782144">"Ayitholakali ngenkathi ushayela"</string>
+    <string name="toolbar_nav_icon_content_description" msgid="2480730986615479860">"Emuva"</string>
+    <string name="toolbar_menu_item_overflow_title" msgid="5386814151990860080">"Ukuphuphuma"</string>
+    <string name="scrollbar_page_down_button" msgid="7317333289151090392">"Skrolela phansi"</string>
+    <string name="scrollbar_page_up_button" msgid="8856997791538307943">"Skrolela phezulu"</string>
+</resources>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/colors.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/colors.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/colors.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values/colors.xml
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/dimens.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/dimens.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/dimens.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values/dimens.xml
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values/strings.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6245387
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <!-- The text on a dialog's dismiss button. [CHAR_LIMIT=50] -->
+  <string name="dialog_dismiss_button">Close</string>
+
+  <!-- Search hint, displayed inside the search box [CHAR LIMIT=50] -->
+  <string name="toolbar_default_search_hint">Search&#8230;</string>
+
+  <!-- Search hint, displayed inside the search box [CHAR LIMIT=50] -->
+  <string name="toolbar_ux_restricted_search_hint">Unavailable while driving</string>
+
+  <!-- The content description on the toolbar back button [CHAR_LIMIT=100] -->
+  <string name="toolbar_nav_icon_content_description">Back</string>
+
+  <!-- The content description of the toolber menu item overflow button [CHAR_LIMIT=50] -->
+  <string name="toolbar_menu_item_overflow_title">Overflow</string>
+
+  <!-- Content description for car ui recycler view scroll bar down arrow [CHAR LIMIT=30] -->
+  <string name="scrollbar_page_down_button">Scroll down</string>
+  <!-- Content description for car ui recycler view scroll bar up arrow [CHAR LIMIT=30] -->
+  <string name="scrollbar_page_up_button">Scroll up</string>
+
+</resources>
diff --git a/car-ui-lib/referencedesign/plugin/src/main/res/values/values-recyclerview.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/values-recyclerview.xml
new file mode 100644
index 0000000..a72763b
--- /dev/null
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values/values-recyclerview.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Whether to display the Scroll Bar or not. Defaults to true. If this is set to false -->
+    <bool name="scrollbar_enable">true</bool>
+
+    <dimen name="scrollbar_margin">112dp</dimen>
+    <dimen name="scrollbar_container_width">112dp</dimen>
+    <dimen name="scrollbar_button_size">76dp</dimen>
+    <dimen name="scrollbar_thumb_width">7dp</dimen>
+    <dimen name="scrollbar_separator_margin">16dp</dimen>
+    <dimen name="scrollbar_min_thumb_height">56dp</dimen>
+
+    <integer name="scrollbar_longpress_initial_delay">1000</integer>
+    <integer name="scrollbar_longpress_repeat_interval">100</integer>
+
+    <item name="button_disabled_alpha" format="float" type="dimen">0.2</item>
+    <item name="scrollbar_milliseconds_per_inch" format="float" type="dimen">150.0</item>
+    <item name="scrollbar_deceleration_times_divisor" format="float" type="dimen">0.45</item>
+    <item name="scrollbar_decelerate_interpolator_factor" format="float" type="dimen">1.8</item>
+
+</resources>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/values-toolbar.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/values-toolbar.xml
similarity index 95%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/values-toolbar.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values/values-toolbar.xml
index 378ed75..ed49782 100644
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/values-toolbar.xml
+++ b/car-ui-lib/referencedesign/plugin/src/main/res/values/values-toolbar.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <drawable name="toolbar_background">#E0000000</drawable>
+
     <bool name="toolbar_title_and_tabs_mutually_exclusive">true</bool>
     <bool name="toolbar_logo_fills_nav_icon_space">true</bool>
     <bool name="toolbar_nav_icon_space_reserved">true</bool>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/values.xml b/car-ui-lib/referencedesign/plugin/src/main/res/values/values.xml
similarity index 100%
rename from car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/values.xml
rename to car-ui-lib/referencedesign/plugin/src/main/res/values/values.xml
diff --git a/car-ui-lib/referencedesign/product.mk b/car-ui-lib/referencedesign/product.mk
index a68e882..0a20795 100644
--- a/car-ui-lib/referencedesign/product.mk
+++ b/car-ui-lib/referencedesign/product.mk
@@ -1,20 +1,11 @@
 # Inherit from this product to include the "Reference Design" RROs for CarUi
 
-#############################################
-#                  WARNING                  #
-#############################################
-# The OEM APIs as they appear on this       #
-# branch of android are not finalized!      #
-# If a shared library is built using them,  #
-# it will cause apps to crash!              #
-#                                           #
-# Please only use a shared library with     #
-# a later version of car-ui-lib.            #
-#############################################
-#PRODUCT_PACKAGES += \
-#   car-ui-lib-sharedlibrary \
+PRODUCT_PACKAGES += \
+   car-ui-lib-plugin-prebuilt \
 
-PRODUCT_PRODUCT_PROPERTIES += ro.build.automotive.car.ui.shared.library.package.name=com.chassis.car.ui.sharedlibrary
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.build.automotive.car.ui.plugin.package.name=com.chassis.car.ui.plugin \
+    persist.sys.automotive.car.ui.plugin.enabled=false \
 
 PRODUCT_COPY_FILES += \
     packages/apps/Car/libs/car-ui-lib/referencedesign/car-ui-lib-preinstalled-packages.xml:system/etc/sysconfig/car-ui-lib-preinstalled-packages.xml \
@@ -32,6 +23,7 @@
     googlecarui-com-android-car-media \
     googlecarui-com-android-car-radio \
     googlecarui-com-android-car-calendar \
+    googlecarui-com-android-car-messenger \
     googlecarui-com-android-car-companiondevicesupport \
     googlecarui-com-android-car-systemupdater \
     googlecarui-com-android-car-dialer \
@@ -39,6 +31,7 @@
     googlecarui-com-android-car-settings \
     googlecarui-com-android-car-voicecontrol \
     googlecarui-com-android-car-faceenroll \
+    googlecarui-com-android-car-developeroptions \
     googlecarui-com-android-managedprovisioning \
     googlecarui-com-android-settings-intelligence \
     googlecarui-com-google-android-apps-automotive-inputmethod \
diff --git a/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml
deleted file mode 100644
index 1f197c0..0000000
--- a/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_inner_ring_color.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="#3D94CBFF"/>
-    <item android:color="@android:color/transparent"/>
-</selector>
diff --git a/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml
deleted file mode 100644
index d03fe13..0000000
--- a/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_outer_ring_color.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_selected="true" android:color="#94CBFF"/>
-    <item android:color="@android:color/transparent"/>
-</selector>
diff --git a/car-telephony-common/tests/robotests/AndroidManifest.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_background_color_selector.xml
similarity index 64%
copy from car-telephony-common/tests/robotests/AndroidManifest.xml
copy to car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_background_color_selector.xml
index 333441a..ae5b017 100644
--- a/car-telephony-common/tests/robotests/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_background_color_selector.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright 2021 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.
@@ -13,10 +13,10 @@
   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.telephony.common.robotests">
-    <application/>
-
-</manifest>
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_selected="true"
+        android:color="@color/car_ui_seekbar_thumb_selected_background_color"/>
+    <item android:color="@android:color/transparent"/>
+</selector>
diff --git a/car-telephony-common/tests/robotests/AndroidManifest.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_stroke_color_selector.xml
similarity index 64%
copy from car-telephony-common/tests/robotests/AndroidManifest.xml
copy to car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_stroke_color_selector.xml
index 333441a..61d3c60 100644
--- a/car-telephony-common/tests/robotests/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selected_stroke_color_selector.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright 2021 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.
@@ -13,10 +13,10 @@
   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.telephony.common.robotests">
-    <application/>
-
-</manifest>
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_selected="true"
+        android:color="@color/car_ui_seekbar_thumb_selected_stroke_color"/>
+    <item android:color="@android:color/transparent"/>
+</selector>
diff --git a/car-telephony-common/tests/robotests/AndroidManifest.xml b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selector.xml
similarity index 64%
rename from car-telephony-common/tests/robotests/AndroidManifest.xml
rename to car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selector.xml
index 333441a..05fcf7a 100644
--- a/car-telephony-common/tests/robotests/AndroidManifest.xml
+++ b/car-ui-lib/referencedesign/res/color/car_ui_seekbar_thumb_selector.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright 2021 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.
@@ -13,10 +13,10 @@
   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.telephony.common.robotests">
-    <application/>
-
-</manifest>
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="false"
+        android:color="@color/car_ui_seekbar_thumb_disabled_on_dark"/>
+    <item android:color="@color/car_ui_seekbar_thumb"/>
+</selector>
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml
index 2d6723e..5b64627 100644
--- a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb.xml
@@ -1,41 +1,41 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 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.
-  -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Padding ensures the intrinsic size of this drawable includes the rings. -->
-    <item>
-        <shape android:shape="oval">
-            <solid android:color="?android:attr/colorAccent"/>
-            <size android:width="24dp" android:height="24dp"/>
-        </shape>
-    </item>
-    <item>
-        <shape android:shape="ring"
-               android:innerRadius="12dp"
-               android:thickness="4dp"
-               android:useLevel="false">
-            <solid android:color="@color/car_ui_seekbar_thumb_inner_ring_color"/>
-        </shape>
-    </item>
-    <item>
-        <shape android:shape="ring"
-               android:innerRadius="16dp"
-               android:thickness="8dp"
-               android:useLevel="false">
-            <solid android:color="@color/car_ui_seekbar_thumb_outer_ring_color"/>
-        </shape>
-    </item>
-</layer-list>
+  Copyright 2021 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"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="52dp"
+    android:height="52dp"
+    android:viewportWidth="52"
+    android:viewportHeight="52">
+    <path
+        android:pathData="M52,26A26,26 0,0 1,26 52,26 26,0 0,1 0,26 26,26 0,0 1,26 0,26 26,0 0,1 52,26Z"
+        android:fillAlpha="0.56">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:gradientRadius="26"
+                android:centerX="26"
+                android:centerY="26"
+                android:type="radial">
+                <item android:offset="0" android:color="#8F000000"/>
+                <item android:offset="0.92307687" android:color="#8F000000"/>
+                <item android:offset="1" android:color="#00000000"/>
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:pathData="M26,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
+        android:fillColor="@color/car_ui_seekbar_thumb_selector"/>
+</vector>
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb_selectable.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb_selectable.xml
new file mode 100644
index 0000000..2cf994e
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_thumb_selectable.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2021 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:drawable="@drawable/car_ui_seekbar_thumb"
+        android:gravity="center" />
+    <item
+        android:gravity="center"
+        android:width="@dimen/car_ui_seekbar_thumb_size"
+        android:height="@dimen/car_ui_seekbar_thumb_size">
+        <shape android:shape="oval">
+            <stroke
+                android:color="@color/car_ui_seekbar_thumb_selected_stroke_color_selector"
+                android:width="@dimen/car_ui_seekbar_thumb_selected_stroke_width" />
+            <solid android:color="@color/car_ui_seekbar_thumb_selected_background_color_selector" />
+        </shape>
+    </item>
+</layer-list>
diff --git a/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_track.xml b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_track.xml
new file mode 100644
index 0000000..0b8f506
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/drawable/car_ui_seekbar_track.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@android:id/background"
+        android:top="@dimen/car_ui_seekbar_track_vertical_padding"
+        android:bottom="@dimen/car_ui_seekbar_track_vertical_padding">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/car_ui_seekbar_thumb_outer_radius" />
+            <size android:height="@dimen/car_ui_seekbar_track_height" />
+            <solid android:color="@color/car_ui_seekbar_track_background" />
+        </shape>
+    </item>
+    <item
+        android:id="@android:id/secondaryProgress"
+        android:top="@dimen/car_ui_seekbar_track_vertical_padding"
+        android:bottom="@dimen/car_ui_seekbar_track_vertical_padding">
+        <scale android:scaleWidth="100%">
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/car_ui_seekbar_thumb_outer_radius" />
+                <size android:height="@dimen/car_ui_seekbar_track_height" />
+                <solid android:color="@color/car_ui_seekbar_track_progress_secondary" />
+            </shape>
+        </scale>
+    </item>
+    <item
+        android:id="@android:id/progress"
+        android:top="@dimen/car_ui_seekbar_track_vertical_padding"
+        android:bottom="@dimen/car_ui_seekbar_track_vertical_padding">
+        <clip>
+            <shape android:shape="rectangle">
+                <corners android:radius="@dimen/car_ui_seekbar_thumb_outer_radius" />
+                <size android:height="@dimen/car_ui_seekbar_track_height" />
+                <solid android:color="@color/car_ui_seekbar_track_progress" />
+            </shape>
+        </clip>
+    </item>
+</layer-list>
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
index d6470eb..7a4fd38 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl-port/car_ui_base_layout_toolbar.xml
@@ -18,6 +18,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:tag="carUiToolbar">
@@ -78,7 +79,8 @@
                     android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
                     android:layout_gravity="center"
                     android:scaleType="fitXY"
-                    android:tint="@color/car_ui_text_color_primary"/>
+                    android:tint="@color/car_ui_text_color_primary"
+                    tools:ignore="UseAppTint" />
 
                 <ImageView
                     android:id="@+id/car_ui_toolbar_logo"
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
index e8b4c36..5f47520 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_base_layout_toolbar.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:tag="CarUiBaseLayoutToolbar">
@@ -63,6 +64,8 @@
                 app:layout_constraintLeft_toLeftOf="parent"
                 app:layout_constraintTop_toTopOf="parent">
 
+                <!-- we need to use android:tint instead of app:tint, because we're
+                    not using classes from androidx/appcompat such as AppCompatImageView -->
                 <ImageView
                     android:id="@+id/car_ui_toolbar_nav_icon"
                     style="@style/Widget.CarUi.Toolbar.NavIcon"
@@ -70,7 +73,8 @@
                     android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
                     android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
                     android:layout_gravity="center"
-                    android:scaleType="fitXY" />
+                    android:scaleType="fitXY"
+                    tools:ignore="UseAppTint" />
 
                 <ImageView
                     android:id="@+id/car_ui_toolbar_logo"
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
deleted file mode 100644
index d5e626a..0000000
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_recyclerview_scrollbar.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?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:layout_width="@dimen/car_ui_scrollbar_container_width"
-    android:layout_height="match_parent"
-    android:id="@+id/car_ui_scroll_bar"
-    android:gravity="center">
-
-    <ImageView
-        android:id="@+id/car_ui_scrollbar_page_up"
-        android:layout_width="@dimen/car_ui_scrollbar_button_size"
-        android:layout_height="@dimen/car_ui_scrollbar_button_size"
-        android:background="@drawable/car_ui_recyclerview_button_ripple_background"
-        android:contentDescription="@string/car_ui_scrollbar_page_up_button"
-        android:focusable="false"
-        android:hapticFeedbackEnabled="false"
-        android:src="@drawable/car_ui_recyclerview_ic_up"
-        android:scaleType="centerInside"
-        android:layout_marginTop="15dp"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
-
-    <!-- View height is dynamically calculated during layout. -->
-    <View
-        android:id="@+id/car_ui_scrollbar_thumb"
-        android:layout_width="@dimen/car_ui_scrollbar_thumb_width"
-        android:layout_height="0dp"
-        android:layout_gravity="center_horizontal"
-        android:background="@drawable/car_ui_recyclerview_scrollbar_thumb"
-        app:layout_constraintTop_toTopOf="@+id/car_ui_scrollbar_track"
-        app:layout_constraintBottom_toBottomOf="@+id/car_ui_scrollbar_track"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
-
-    <View
-        android:id="@+id/car_ui_scrollbar_track"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_marginTop="@dimen/car_ui_scrollbar_separator_margin"
-        android:layout_marginBottom="@dimen/car_ui_scrollbar_separator_margin"
-        app:layout_constraintTop_toBottomOf="@+id/car_ui_scrollbar_page_up"
-        app:layout_constraintBottom_toTopOf="@+id/car_ui_scrollbar_page_down"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-    <ImageView
-        android:id="@+id/car_ui_scrollbar_page_down"
-        android:layout_width="@dimen/car_ui_scrollbar_button_size"
-        android:layout_height="@dimen/car_ui_scrollbar_button_size"
-        android:background="@drawable/car_ui_recyclerview_button_ripple_background"
-        android:contentDescription="@string/car_ui_scrollbar_page_down_button"
-        android:focusable="false"
-        android:hapticFeedbackEnabled="false"
-        android:src="@drawable/car_ui_recyclerview_ic_down"
-        android:scaleType="centerInside"
-        android:layout_marginBottom="15dp"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"/>
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
index 2714f44..f9cd6df 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/car_ui_toolbar_background"
     android:layout_width="match_parent"
     android:layout_height="96dp">
@@ -37,7 +38,8 @@
             android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
             android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
             android:layout_gravity="center"
-            android:scaleType="fitXY" />
+            android:scaleType="fitXY"
+            tools:ignore="UseAppTint" />
 
         <ImageView
             android:id="@+id/car_ui_toolbar_logo"
diff --git a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
index 6935121..69c48e1 100644
--- a/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
+++ b/car-ui-lib/referencedesign/res/layout-ldrtl/car_ui_toolbar_two_row.xml
@@ -17,6 +17,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/car_ui_toolbar_background"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
@@ -44,7 +45,8 @@
             android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
             android:layout_gravity="center"
             android:scaleType="fitXY"
-            android:tint="@color/car_ui_text_color_primary"/>
+            android:tint="@color/car_ui_text_color_primary"
+            tools:ignore="UseAppTint" />
 
         <ImageView
             android:id="@+id/car_ui_toolbar_logo"
diff --git a/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
index d8b6d93..a994d32 100644
--- a/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
+++ b/car-ui-lib/referencedesign/res/layout-port/car_ui_base_layout_toolbar.xml
@@ -18,6 +18,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:tag="CarUiBaseLayoutToolbar">
@@ -71,6 +72,8 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent">
 
+                <!-- we need to use android:tint instead of app:tint, because we're
+                    not using classes from androidx/appcompat such as AppCompatImageView -->
                 <ImageView
                     android:id="@+id/car_ui_toolbar_nav_icon"
                     style="@style/Widget.CarUi.Toolbar.NavIcon"
@@ -78,7 +81,8 @@
                     android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
                     android:layout_gravity="center"
                     android:scaleType="fitXY"
-                    android:tint="@color/car_ui_text_color_primary"/>
+                    android:tint="@color/car_ui_text_color_primary"
+                    tools:ignore="UseAppTint"/>
 
                 <ImageView
                     android:id="@+id/car_ui_toolbar_logo"
diff --git a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml b/car-ui-lib/referencedesign/res/layout/car_ui_alert_dialog_list.xml
similarity index 72%
copy from car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
copy to car-ui-lib/referencedesign/res/layout/car_ui_alert_dialog_list.xml
index 6a35b43..e37e651 100644
--- a/car-ui-lib/car-ui-lib/src/main/res/layout/car_ui_recycler_view_item.xml
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_alert_dialog_list.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright 2019 The Android Open Source Project
+  ~ Copyright 2020 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.
@@ -15,10 +15,11 @@
   ~ limitations under the License.
   -->
 
-<FrameLayout
+<androidx.recyclerview.widget.RecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/nested_recycler_view_layout"
+    android:id="@+id/list"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:gravity="center">
-</FrameLayout>
+    android:layout_height="match_parent">
+</androidx.recyclerview.widget.RecyclerView>
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_base_layout_toolbar.xml b/car-ui-lib/referencedesign/res/layout/car_ui_base_layout_toolbar.xml
new file mode 100644
index 0000000..4db61e7
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_base_layout_toolbar.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:tag="CarUiBaseLayoutToolbar">
+
+    <!-- When not in touch mode, if we clear focus in current window, Android will re-focus the
+         first focusable view in the window automatically. Adding a FocusParkingView to the window
+         can fix this issue, because it can take focus, and it is transparent and its default focus
+         highlight is disabled, so it's invisible to the user no matter whether it's focused or not.
+         -->
+    <com.android.car.ui.FocusParkingView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <FrameLayout
+        android:id="@+id/car_ui_base_layout_content_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <com.android.car.ui.FocusArea
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/car_ui_toolbar_first_row_height">
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/car_ui_toolbar_background"
+            style="@style/Widget.CarUi.Toolbar.Container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:tag="car_ui_top_inset"
+            app:layout_constraintTop_toTopOf="parent">
+            <com.android.car.ui.baselayout.ClickBlockingView
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"/>
+
+            <!-- The horizontal bias set to 0.0 here is so that when you set this view as GONE, it
+                 will be treated as if it's all the way to the left instead of centered in the
+                 margin -->
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_nav_icon_container"
+                style="@style/Widget.CarUi.Toolbar.NavIconContainer"
+                android:layout_width="@dimen/car_ui_toolbar_margin"
+                android:layout_height="0dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_nav_icon"
+                    style="@style/Widget.CarUi.Toolbar.NavIcon"
+                    android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
+                    android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY"/>
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_logo"
+                    android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY"/>
+            </FrameLayout>
+
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_title_logo_container"
+                style="@style/Widget.CarUi.Toolbar.LogoContainer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container"
+                app:layout_constraintTop_toTopOf="parent">
+
+                <ImageView
+                    android:id="@+id/car_ui_toolbar_title_logo"
+                    android:layout_margin="@dimen/car_ui_toolbar_logo_margin"
+                    style="@style/Widget.CarUi.Toolbar.Logo"
+                    android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                    android:layout_gravity="center"
+                    android:scaleType="fitXY"/>
+            </FrameLayout>
+
+            <LinearLayout android:layout_height="wrap_content"
+                android:layout_width="0dp"
+                android:id="@+id/car_ui_toolbar_title_container"
+                android:orientation="vertical"
+                android:layout_marginStart="@dimen/car_ui_toolbar_title_margin_start"
+                app:layout_goneMarginStart="@dimen/car_ui_toolbar_title_no_logo_margin_start"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+                app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+                app:layout_constraintTop_toTopOf="parent">
+                <TextView android:id="@+id/car_ui_toolbar_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    style="@style/Widget.CarUi.Toolbar.Title"/>
+                <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    style="@style/Widget.CarUi.Toolbar.Subtitle"/>
+            </LinearLayout>
+
+            <com.android.car.ui.toolbar.TabLayout
+                android:id="@+id/car_ui_toolbar_tabs"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+                app:layout_constraintHorizontal_bias="0.0"
+                app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+                app:layout_constraintTop_toTopOf="parent"/>
+
+            <LinearLayout
+                android:id="@+id/car_ui_toolbar_menu_items_container"
+                style="@style/Widget.CarUi.Toolbar.MenuItem.Container"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                android:layout_marginRight="@dimen/car_ui_padding_7"/>
+
+            <FrameLayout
+                android:id="@+id/car_ui_toolbar_search_view_container"
+                android:layout_width="0dp"
+                android:layout_height="@dimen/car_ui_toolbar_search_height"
+                android:layout_marginStart="@dimen/car_ui_toolbar_title_margin_start"
+                app:layout_goneMarginStart="@dimen/car_ui_toolbar_title_no_logo_margin_start"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+                app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+                app:layout_constraintTop_toTopOf="parent"/>
+
+            <ProgressBar
+                android:id="@+id/car_ui_toolbar_progress_bar"
+                style="@style/Widget.CarUi.Toolbar.ProgressBar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:indeterminate="true"
+                android:visibility="gone"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.car.ui.FocusArea>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml b/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml
index 16d4ea0..9ff3564 100644
--- a/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_preference_widget_seekbar.xml
@@ -28,7 +28,7 @@
     android:clipChildren="false"
     android:clipToPadding="false">
 
-    <ImageView
+    <com.android.car.ui.uxr.DrawableStateImageView
         android:id="@android:id/icon"
         android:layout_width="44dp"
         android:layout_height="44dp"
@@ -47,14 +47,14 @@
         android:clipChildren="false"
         android:clipToPadding="false">
 
-        <TextView
+        <com.android.car.ui.uxr.DrawableStateTextView
             android:id="@android:id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:textAppearance="@style/TextAppearance.CarUi.PreferenceTitle"/>
 
-        <TextView
+        <com.android.car.ui.uxr.DrawableStateTextView
             android:id="@android:id/summary"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar.xml
new file mode 100644
index 0000000..2aa6d2b
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar.xml
@@ -0,0 +1,183 @@
+<?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.
+-->
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_ui_toolbar_first_row_height">
+    <androidx.constraintlayout.widget.ConstraintLayout
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/car_ui_toolbar_background"
+        android:tag="carUiToolbar"
+        style="@style/Widget.CarUi.Toolbar.Container">
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_start_guideline"
+            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_start_inset"
+            android:orientation="vertical"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_top_guideline"
+            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_top_inset"
+            android:orientation="horizontal"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_end_guideline"
+            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_end_inset"
+            android:orientation="vertical"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_bottom_inset"
+            android:orientation="horizontal"/>
+
+        <!-- The horizontal bias here is so that when you set this view as GONE, it will be
+             treated as if it's all the way to the left instead of centered in the margin -->
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_width="@dimen/car_ui_toolbar_margin"
+            android:layout_height="0dp"
+            style="@style/Widget.CarUi.Toolbar.NavIconContainer"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_start_guideline"
+            app:layout_constraintHorizontal_bias="0.0">
+            <ImageView
+                android:id="@+id/car_ui_toolbar_nav_icon"
+                android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
+                android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                style="@style/Widget.CarUi.Toolbar.NavIcon"/>
+            <ImageView
+                android:id="@+id/car_ui_toolbar_logo"
+                android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"/>
+        </FrameLayout>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_title_logo_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/Widget.CarUi.Toolbar.LogoContainer"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_title_logo"
+                android:layout_margin="@dimen/car_ui_toolbar_logo_margin"
+                android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                android:scaleType="fitXY"
+                android:layout_gravity="center"
+                style="@style/Widget.CarUi.Toolbar.Logo"/>
+        </FrameLayout>
+
+        <LinearLayout android:layout_height="wrap_content"
+                      android:layout_width="0dp"
+                      android:id="@+id/car_ui_toolbar_title_container"
+                      android:orientation="vertical"
+                      android:layout_marginStart="@dimen/car_ui_toolbar_title_margin_start"
+                      app:layout_goneMarginStart="@dimen/car_ui_toolbar_title_no_logo_margin_start"
+                      app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+                      app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+                      app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+                      app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container">
+            <TextView android:id="@+id/car_ui_toolbar_title"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:singleLine="true"
+                      style="@style/Widget.CarUi.Toolbar.Title"/>
+            <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:visibility="gone"
+                      style="@style/Widget.CarUi.Toolbar.Subtitle"/>
+        </LinearLayout>
+
+        <com.android.car.ui.toolbar.TabLayout
+            android:id="@+id/car_ui_toolbar_tabs"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_title_logo_container"
+            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"
+            app:layout_constraintHorizontal_bias="0.0"/>
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:orientation="horizontal"
+            style="@style/Widget.CarUi.Toolbar.MenuItem.Container"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_end_guideline"/>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_search_view_container"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/car_ui_toolbar_search_height"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"/>
+
+        <View
+            android:id="@+id/car_ui_toolbar_row_separator"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_ui_toolbar_separator_height"
+            style="@style/Widget.CarUi.Toolbar.SeparatorView"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <ProgressBar
+            android:id="@+id/car_ui_toolbar_progress_bar"
+            style="@style/Widget.CarUi.Toolbar.ProgressBar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:indeterminate="true"
+            android:visibility="gone"/>
+
+        <View
+            android:id="@+id/car_ui_toolbar_bottom_styleable"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_ui_toolbar_bottom_view_height"
+            style="@style/Widget.CarUi.Toolbar.BottomView"
+            app:layout_constraintBottom_toTopOf="@+id/car_ui_toolbar_progress_bar"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.car.ui.FocusArea>
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
index 0a6b24c..3554e7d 100644
--- a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item.xml
@@ -16,6 +16,7 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:focusable="false">
@@ -37,7 +38,8 @@
             android:layout_height="44dp"
             android:layout_gravity="center"
             android:tint="@color/car_ui_toolbar_menu_item_icon_color"
-            android:tintMode="src_in"/>
+            android:tintMode="src_in"
+            tools:ignore="UseAppTint" />
     </FrameLayout>
     <com.android.car.ui.uxr.DrawableStateSwitch
         android:id="@+id/car_ui_toolbar_menu_item_switch"
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml
index 716c8ee..bdc3238 100644
--- a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_menu_item_primary.xml
@@ -16,6 +16,7 @@
 -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:focusable="false">
@@ -37,7 +38,8 @@
             android:layout_height="44dp"
             android:layout_gravity="center"
             android:tint="@color/car_ui_toolbar_menu_item_icon_color"
-            android:tintMode="src_in"/>
+            android:tintMode="src_in"
+            tools:ignore="UseAppTint" />
     </FrameLayout>
     <com.android.car.ui.uxr.DrawableStateSwitch
         android:id="@+id/car_ui_toolbar_menu_item_switch"
diff --git a/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_two_row.xml b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_two_row.xml
new file mode 100644
index 0000000..74e8d58
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/layout/car_ui_toolbar_two_row.xml
@@ -0,0 +1,183 @@
+<?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.
+-->
+<com.android.car.ui.FocusArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <androidx.constraintlayout.widget.ConstraintLayout
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/car_ui_toolbar_background"
+        style="@style/Widget.CarUi.Toolbar.Container">
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_start_guideline"
+            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_start_inset"
+            android:orientation="vertical"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_top_guideline"
+            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_top_inset"
+            android:orientation="horizontal"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_end_guideline"
+            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_end_inset"
+            android:orientation="vertical"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/car_ui_toolbar_bottom_guideline"
+            app:layout_constraintGuide_end="@dimen/car_ui_toolbar_bottom_inset"
+            android:orientation="horizontal"/>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/car_ui_toolbar_row_separator_guideline"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:orientation="horizontal"
+            app:layout_constraintGuide_begin="@dimen/car_ui_toolbar_first_row_height"/>
+
+        <View
+            android:id="@+id/car_ui_toolbar_row_separator"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_ui_toolbar_separator_height"
+            style="@style/Widget.CarUi.Toolbar.SeparatorView"
+            app:layout_constraintTop_toBottomOf="@id/car_ui_toolbar_row_separator_guideline"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_nav_icon_container"
+            android:layout_width="@dimen/car_ui_toolbar_margin"
+            android:layout_height="0dp"
+            style="@style/Widget.CarUi.Toolbar.NavIconContainer"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintStart_toStartOf="@id/car_ui_toolbar_start_guideline">
+            <ImageView
+                android:id="@+id/car_ui_toolbar_nav_icon"
+                android:layout_width="@dimen/car_ui_toolbar_nav_icon_size"
+                android:layout_height="@dimen/car_ui_toolbar_nav_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"
+                style="@style/Widget.CarUi.Toolbar.NavIcon"/>
+            <ImageView
+                android:id="@+id/car_ui_toolbar_logo"
+                android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                android:layout_gravity="center"
+                android:scaleType="fitXY"/>
+        </FrameLayout>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_title_logo_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/Widget.CarUi.Toolbar.LogoContainer"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_nav_icon_container">
+
+            <ImageView
+                android:id="@+id/car_ui_toolbar_title_logo"
+                android:layout_margin="@dimen/car_ui_toolbar_logo_margin"
+                android:layout_width="@dimen/car_ui_toolbar_logo_size"
+                android:layout_height="@dimen/car_ui_toolbar_logo_size"
+                android:scaleType="fitXY"
+                android:layout_gravity="center"
+                style="@style/Widget.CarUi.Toolbar.Logo"/>
+        </FrameLayout>
+
+        <LinearLayout android:layout_height="wrap_content"
+                      android:layout_width="0dp"
+                      android:id="@+id/car_ui_toolbar_title_container"
+                      android:orientation="vertical"
+                      android:layout_marginStart="@dimen/car_ui_toolbar_title_margin_start"
+                      app:layout_goneMarginStart="@dimen/car_ui_toolbar_title_no_logo_margin_start"
+                      app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+                      app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+                      app:layout_constraintStart_toEndOf="@id/car_ui_toolbar_title_logo_container"
+                      app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_menu_items_container">
+            <TextView android:id="@+id/car_ui_toolbar_title"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:singleLine="true"
+                      style="@style/Widget.CarUi.Toolbar.Title"/>
+            <TextView android:id="@+id/car_ui_toolbar_subtitle"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:visibility="gone"
+                      style="@style/Widget.CarUi.Toolbar.Subtitle"/>
+        </LinearLayout>
+
+        <FrameLayout
+            android:id="@+id/car_ui_toolbar_search_view_container"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/car_ui_toolbar_search_height"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintStart_toEndOf="@+id/car_ui_toolbar_nav_icon_container"
+            app:layout_constraintEnd_toStartOf="@+id/car_ui_toolbar_menu_items_container"/>
+
+        <LinearLayout
+            android:id="@+id/car_ui_toolbar_menu_items_container"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:orientation="horizontal"
+            style="@style/Widget.CarUi.Toolbar.MenuItem.Container"
+            app:layout_constraintTop_toTopOf="@id/car_ui_toolbar_top_guideline"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintEnd_toStartOf="@id/car_ui_toolbar_end_guideline"/>
+
+        <com.android.car.ui.toolbar.TabLayout
+            android:id="@+id/car_ui_toolbar_tabs"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_ui_toolbar_second_row_height"
+            app:layout_constraintTop_toBottomOf="@id/car_ui_toolbar_row_separator"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_guideline"/>
+
+        <View
+            android:id="@+id/car_ui_toolbar_bottom_styleable"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_ui_toolbar_bottom_view_height"
+            style="@style/Widget.CarUi.Toolbar.BottomView"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <ProgressBar
+            android:id="@+id/car_ui_toolbar_progress_bar"
+            style="@style/Widget.CarUi.Toolbar.ProgressBar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintBottom_toTopOf="@id/car_ui_toolbar_bottom_styleable"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:indeterminate="true"
+            android:visibility="gone"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.car.ui.FocusArea>
diff --git a/car-ui-lib/referencedesign/res/values-ldrtl/values.xml b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
index 24552a5..b3d46a5 100644
--- a/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
+++ b/car-ui-lib/referencedesign/res/values-ldrtl/values.xml
@@ -2,16 +2,5 @@
     <bool name="car_ui_toolbar_nav_icon_reserve_space">false</bool>
     <bool name="car_ui_toolbar_logo_fills_nav_icon_space">false</bool>
 
-    <dimen name="car_ui_scrollbar_margin">112dp</dimen>
-    <dimen name="car_ui_scrollbar_container_width">112dp</dimen>
-    <dimen name="car_ui_scrollbar_button_size">76dp</dimen>
-    <dimen name="car_ui_scrollbar_thumb_width">7dp</dimen>
-    <dimen name="car_ui_scrollbar_separator_margin">16dp</dimen>
-
-    <string name="car_ui_scrollbar_page_up_button">Scroll up</string>
-    <string name="car_ui_scrollbar_page_down_button">Scroll down</string>
-
     <color name="car_ui_ripple_color">#27ffffff</color>
-    <color name="car_ui_scrollbar_thumb">#99ffffff</color>
-    <dimen name="car_ui_scrollbar_thumb_radius">100dp</dimen>
 </resources>
diff --git a/car-ui-lib/referencedesign/res/values/colors.xml b/car-ui-lib/referencedesign/res/values/colors.xml
new file mode 100644
index 0000000..39bb196
--- /dev/null
+++ b/car-ui-lib/referencedesign/res/values/colors.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+
+    <!-- The Switch track's background color. -->
+    <color name="car_ui_seekbar_track_background">#757575</color>
+    <!-- The Switch track's progress color. -->
+    <color name="car_ui_seekbar_track_progress">#66B5FF</color>
+    <!-- The Switch track's secondary progress color. -->
+    <color name="car_ui_seekbar_track_progress_secondary">#A6A6A9</color>
+    <!-- Color of the outer ring when the SeekBar is selected. -->
+    <color name="car_ui_seekbar_thumb_selected_stroke_color">#94CBFF</color>
+    <!-- Color of the background of the ring when the SeekBar is selected. -->
+    <color name="car_ui_seekbar_thumb_selected_background_color">#3D94CBFF</color>
+    <!-- The SeekBar thumb color. -->
+    <color name="car_ui_seekbar_thumb">#FFFFFF</color>
+    <!-- The SeekBar thumb color when disabled. Use for the dark theme. -->
+    <color name="car_ui_seekbar_thumb_disabled_on_dark">#757575</color>
+</resources>
diff --git a/car-ui-lib/referencedesign/res/values/dimens.xml b/car-ui-lib/referencedesign/res/values/dimens.xml
index 80fc15b..e18a6ae 100644
--- a/car-ui-lib/referencedesign/res/values/dimens.xml
+++ b/car-ui-lib/referencedesign/res/values/dimens.xml
@@ -15,4 +15,67 @@
 -->
 <resources>
     <dimen name="car_ui_toolbar_margin">76dp</dimen>
+    <!-- Paddings -->
+    <dimen name="car_ui_padding_0">4dp</dimen>
+    <dimen name="car_ui_padding_1">8dp</dimen>
+    <dimen name="car_ui_padding_2">12dp</dimen>
+    <dimen name="car_ui_padding_3">16dp</dimen>
+    <dimen name="car_ui_padding_4">24dp</dimen>
+    <dimen name="car_ui_padding_5">32dp</dimen>
+    <dimen name="car_ui_padding_6">48dp</dimen>
+    <dimen name="car_ui_padding_7">64dp</dimen>
+    <dimen name="car_ui_padding_8">96dp</dimen>
+
+    <!-- SeekBar dimensions. -->
+    <!-- Allows thumb to extend out of the range of the track. -->
+    <!-- For more information see android.widget.SeekBar#attr_android:thumbOffset. -->
+    <dimen name="car_ui_seekbar_thumb_offset">24dp</dimen>
+    <!-- Horizontal padding for the SeekBar.-->
+    <dimen name="car_ui_seekbar_horizontal_padding">24dp</dimen>
+    <!-- The SeekBar track height.-->
+    <dimen name="car_ui_seekbar_track_height">24dp</dimen>
+    <!-- Vertical padding that extends the touch area for the SeekBar. -->
+    <dimen name="car_ui_seekbar_track_vertical_padding">26dp</dimen>
+    <!-- The stroke with of the ring around the SeekBar thumb when selected. -->
+    <dimen name="car_ui_seekbar_thumb_selected_stroke_width">8dp</dimen>
+    <!-- The size of the SeekBar thumb. -->
+    <dimen name="car_ui_seekbar_thumb_size">68dp</dimen>
+    <!-- Should be the half of car_switch_track_height. -->
+    <dimen name="car_ui_seekbar_thumb_outer_radius">24dp</dimen>
+
+    <!-- Height of the top toolbar row. This can be customized independently. -->
+    <dimen name="car_ui_toolbar_first_row_height">@dimen/car_ui_toolbar_row_height</dimen>
+    <!-- Margin at the start of the title when there is no logo present -->
+    <dimen name="car_ui_toolbar_title_no_logo_margin_start">17dp</dimen>
+    <!-- Margin at the start of the title -->
+    <dimen name="car_ui_toolbar_title_margin_start">17dp</dimen>
+    <!-- Height of the search box -->
+    <dimen name="car_ui_toolbar_search_height">0dp</dimen>
+    <!-- Padding on the toolbar start (e.g.: distance between the container start and the start of
+    nav icon or logo) -->
+    <dimen name="car_ui_toolbar_start_inset">0dp</dimen>
+    <!-- Top padding -->
+    <dimen name="car_ui_toolbar_top_inset">0dp</dimen>
+    <!-- End padding (e.g.: distance between the container end and the end of the menu items) -->
+    <dimen name="car_ui_toolbar_end_inset">0dp</dimen>
+    <!-- Bottom padding -->
+    <dimen name="car_ui_toolbar_bottom_inset">0dp</dimen>
+
+    <!-- can't use 0dp for layout_height or the constraintlayout effect kicks in -->
+    <dimen name="car_ui_toolbar_separator_height">0.1dp</dimen>
+    <!-- can't use 0dp for layout_height or the constraintlayout effect kicks in -->
+    <dimen name="car_ui_toolbar_bottom_view_height">0.1dp</dimen>
+    <!-- Height of the bottom toolbar row (if the toolbar is used in two-rows mode. -->
+    <dimen name="car_ui_toolbar_second_row_height">@dimen/car_ui_toolbar_row_height</dimen>
+
+    <!-- Default height for both toolbar rows. See car_ui_toolbar_first_row_height and
+     car_ui_toolbar_second_row_height -->
+    <dimen name="car_ui_toolbar_row_height">96dp</dimen>
+    <!-- Margin between the logo and the title, when both logo and navigation icons are used -->
+    <dimen name="car_ui_toolbar_title_logo_padding">0dp</dimen>
+    <!-- Margin on the outside the toolbar logo -->
+    <dimen name="car_ui_toolbar_logo_margin">17dp</dimen>
+    <!-- Icon size for icon menu items -->
+    <dimen name="car_ui_toolbar_menu_item_icon_size">36dp</dimen>
+
 </resources>
diff --git a/car-ui-lib/referencedesign/res/values/styles.xml b/car-ui-lib/referencedesign/res/values/styles.xml
index d46763f..1e6102c 100644
--- a/car-ui-lib/referencedesign/res/values/styles.xml
+++ b/car-ui-lib/referencedesign/res/values/styles.xml
@@ -53,11 +53,14 @@
     <style name="Widget.CarUi.Toolbar.TextButton.WithIcon">
         <item name="android:textColor">@color/car_ui_toolbar_menu_item_icon_color</item>
     </style>
-
     <style name="Widget.CarUi.Toolbar.TextButton.Primary" parent="Widget.CarUi.Button">
         <item name="android:drawableTint">@color/car_ui_toolbar_menu_item_icon_color</item>
-        <item name="android:drawablePadding">10dp</item>
         <item name="android:maxWidth">350dp</item>
+        <item name="android:drawablePadding">@dimen/car_ui_padding_2</item>
+        <item name="android:paddingBottom">@dimen/car_ui_padding_2</item>
+        <item name="android:paddingTop">@dimen/car_ui_padding_2</item>
+        <item name="android:paddingStart">@dimen/car_ui_padding_3</item>
+        <item name="android:paddingEnd">@dimen/car_ui_padding_4</item>
     </style>
 
     <style name="Widget.CarUi.SeekbarPreference"/>
@@ -67,8 +70,15 @@
         <item name="android:background">@null</item>
         <item name="android:clickable">false</item>
         <item name="android:focusable">false</item>
-        <item name="android:thumb">@drawable/car_ui_seekbar_thumb</item>
+        <item name="android:thumb">@drawable/car_ui_seekbar_thumb_selectable</item>
+        <item name="android:progressDrawable">@drawable/car_ui_seekbar_track</item>
         <item name="android:splitTrack">false</item>
+        <item name="android:thumbOffset">@dimen/car_ui_seekbar_thumb_offset</item>
+        <item name="android:paddingStart">@dimen/car_ui_seekbar_horizontal_padding</item>
+        <item name="android:paddingEnd">@dimen/car_ui_seekbar_horizontal_padding</item>
+        <item name="*android:useDisabledAlpha">true</item>
+        <item name="android:clipToOutline">false</item>
+        <item name="android:clipToPadding">false</item>
     </style>
 
     <style name="TextAppearance.CarUi" parent="android:TextAppearance.DeviceDefault">
@@ -94,4 +104,35 @@
         <item name="android:textColor">@color/car_ui_text_color_secondary</item>
     </style>
 
+    <style name="Widget.CarUi.Toolbar.Container"></style>
+    <style name="Widget.CarUi.Toolbar.LogoContainer">
+        <item name="android:paddingEnd">@dimen/car_ui_toolbar_title_logo_padding</item>
+    </style>
+    <style name="Widget.CarUi.Toolbar.Logo"></style>
+    <style name="Widget.CarUi.Toolbar.Title">
+        <item name="android:textAppearance">@style/TextAppearance.CarUi.Widget.Toolbar.Title</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+    <style name="Widget.CarUi.Toolbar.Subtitle">
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+    <style name="Widget.CarUi.Toolbar.MenuItem.Container">
+        <item name="android:divider">@drawable/car_ui_toolbar_menu_item_divider</item>
+        <item name="android:showDividers">beginning|middle|end</item>
+    </style>
+    <style name="Widget.CarUi.Toolbar.ProgressBar"
+        parent="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"/>
+
+    <style name="Widget.CarUi.Toolbar.SeparatorView">
+        <item name="android:height">0.01dp</item>
+        <item name="android:background">@android:color/transparent</item>
+    </style>
+    <style name="Widget.CarUi.Toolbar.BottomView">
+        <item name="android:height">0.01dp</item>
+        <item name="android:background">@android:color/transparent</item>
+    </style>
+
+
+
 </resources>
diff --git a/car-ui-lib/referencedesign/res/xml/overlays.xml b/car-ui-lib/referencedesign/res/xml/overlays.xml
index f870c48..b7b16e1 100644
--- a/car-ui-lib/referencedesign/res/xml/overlays.xml
+++ b/car-ui-lib/referencedesign/res/xml/overlays.xml
@@ -1,3 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
 <overlay>
     <item target="attr/layout_constraintBottom_toBottomOf" value="@attr/layout_constraintBottom_toBottomOf"/>
     <item target="attr/layout_constraintBottom_toTopOf" value="@attr/layout_constraintBottom_toTopOf"/>
@@ -33,18 +48,13 @@
     <item target="color/car_ui_toolbar_menu_item_icon_color" value="@color/car_ui_toolbar_menu_item_icon_color"/>
 
     <item target="dimen/car_ui_toolbar_margin" value="@dimen/car_ui_toolbar_margin"/>
+    <item target="dimen/car_ui_toolbar_menu_item_icon_size" value="@dimen/car_ui_toolbar_menu_item_icon_size"/>
 
     <item target="drawable/car_ui_icon_arrow_back" value="@drawable/car_ui_icon_arrow_back"/>
     <item target="drawable/car_ui_toolbar_menu_item_icon_background" value="@drawable/car_ui_toolbar_menu_item_icon_background"/>
     <item target="drawable/car_ui_toolbar_menu_item_icon_ripple" value="@drawable/car_ui_toolbar_menu_item_icon_ripple"/>
 
     <item target="id/car_ui_base_layout_content_container" value="@id/car_ui_base_layout_content_container" />
-    <item target="id/car_ui_recycler_view" value="@id/car_ui_recycler_view" />
-    <item target="id/car_ui_scrollbar_page_down" value="@id/car_ui_scrollbar_page_down"/>
-    <item target="id/car_ui_scrollbar_page_up" value="@id/car_ui_scrollbar_page_up"/>
-    <item target="id/car_ui_scrollbar_thumb" value="@id/car_ui_scrollbar_thumb"/>
-    <item target="id/car_ui_scrollbar_track" value="@id/car_ui_scrollbar_track"/>
-    <item target="id/car_ui_scroll_bar" value="@id/car_ui_scroll_bar"/>
     <item target="id/car_ui_toolbar_background" value="@id/car_ui_toolbar_background" />
     <item target="id/car_ui_toolbar_logo" value="@id/car_ui_toolbar_logo" />
     <item target="id/car_ui_toolbar_menu_item_icon_container" value="@id/car_ui_toolbar_menu_item_icon_container"/>
@@ -63,12 +73,13 @@
     <item target="id/car_ui_toolbar_title_logo_container" value="@id/car_ui_toolbar_title_logo_container" />
     <item target="id/car_ui_toolbar_title_logo" value="@id/car_ui_toolbar_title_logo" />
     <item target="id/car_ui_toolbar_title" value="@id/car_ui_toolbar_title" />
+    <item target="id/list" value="@id/list"/>
     <item target="id/seekbar" value="@id/seekbar"/>
     <item target="id/seekbar_value" value="@id/seekbar_value"/>
 
+    <item target="layout/car_ui_alert_dialog_list" value="@layout/car_ui_alert_dialog_list"/>
     <item target="layout/car_ui_base_layout_toolbar" value="@layout/car_ui_base_layout_toolbar"/>
     <item target="layout/car_ui_preference_widget_seekbar" value="@layout/car_ui_preference_widget_seekbar"/>
-    <item target="layout/car_ui_recycler_view" value="@layout/car_ui_recycler_view"/>
     <item target="layout/car_ui_toolbar_menu_item_primary" value="@layout/car_ui_toolbar_menu_item_primary"/>
     <item target="layout/car_ui_toolbar_menu_item" value="@layout/car_ui_toolbar_menu_item"/>
     <item target="layout/car_ui_toolbar_two_row" value="@layout/car_ui_toolbar_two_row"/>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/android/car/ui/sharedlibrary/SharedLibraryVersionProviderImpl.java b/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/android/car/ui/sharedlibrary/SharedLibraryVersionProviderImpl.java
deleted file mode 100644
index 038dc6e..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/android/car/ui/sharedlibrary/SharedLibraryVersionProviderImpl.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 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.ui.sharedlibrary;
-
-import android.content.Context;
-
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryVersionProviderOEMV1;
-
-import com.chassis.car.ui.sharedlibrary.SharedLibraryFactoryImpl;
-
-/**
- * An implementation of {@link SharedLibraryVersionProviderOEMV1} for the reference design
- * shared library.
- */
-public class SharedLibraryVersionProviderImpl implements SharedLibraryVersionProviderOEMV1 {
-
-    @Override
-    public Object getSharedLibraryFactory(int maxVersion, Context context, String packageName) {
-        return new SharedLibraryFactoryImpl(context);
-    }
-}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/SharedLibraryFactoryImpl.java b/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/SharedLibraryFactoryImpl.java
deleted file mode 100644
index bfbf7dd..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/SharedLibraryFactoryImpl.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2020 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.chassis.car.ui.sharedlibrary;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.car.ui.sharedlibrary.oemapis.FocusAreaOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.FocusParkingViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.InsetsOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.SharedLibraryFactoryOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ListItemOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.ViewHolderOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.ToolbarControllerOEMV1;
-
-import com.chassis.car.ui.sharedlibrary.toolbar.BaseLayoutInstaller;
-
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-/**
- * An implementation of {@link SharedLibraryFactoryImpl} for creating the reference design
- * car-ui-lib components.
- */
-@SuppressWarnings("AndroidJdkLibsChecker")
-public class SharedLibraryFactoryImpl implements SharedLibraryFactoryOEMV1 {
-
-    private final Context mSharedLibraryContext;
-    @Nullable
-    private Function<Context, FocusParkingViewOEMV1> mFocusParkingViewFactory;
-    @Nullable
-    private Function<Context, FocusAreaOEMV1> mFocusAreaFactory;
-
-    public SharedLibraryFactoryImpl(Context sharedLibraryContext) {
-        mSharedLibraryContext = sharedLibraryContext;
-    }
-
-    @Override
-    public void setRotaryFactories(
-            Function<Context, FocusParkingViewOEMV1> focusParkingViewFactory,
-            Function<Context, FocusAreaOEMV1> focusAreaFactory) {
-        mFocusParkingViewFactory = focusParkingViewFactory;
-        mFocusAreaFactory = focusAreaFactory;
-    }
-
-    @Override
-    public ToolbarControllerOEMV1 installBaseLayoutAround(View contentView,
-            Consumer<InsetsOEMV1> insetsChangedListener, boolean toolbarEnabled,
-            boolean fullscreen) {
-
-        return BaseLayoutInstaller.installBaseLayoutAround(
-                mSharedLibraryContext,
-                contentView,
-                insetsChangedListener,
-                toolbarEnabled,
-                fullscreen,
-                mFocusParkingViewFactory,
-                mFocusAreaFactory);
-    }
-
-    @Override
-    public boolean customizesBaseLayout() {
-        return true;
-    }
-
-    @Override
-    public AppStyledViewControllerOEMV1 createAppStyledView() {
-        //return new AppStyleViewControllerImpl(mSharedLibraryContext);
-        return null;
-    }
-
-    @Override
-    public RecyclerViewOEMV1 createRecyclerView(Context context,
-            RecyclerViewAttributesOEMV1 attrs) {
-        //return new RecyclerViewImpl(context, attrs);
-        return null;
-    }
-
-    @Override
-    public AdapterOEMV1<? extends ViewHolderOEMV1> createListItemAdapter(
-            List<ListItemOEMV1> items) {
-        //return new ListItemAdapter(items);
-        return null;
-    }
-}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/appstyleview/AppStyleViewControllerImpl.java b/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/appstyleview/AppStyleViewControllerImpl.java
deleted file mode 100644
index 14dee37..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/appstyleview/AppStyleViewControllerImpl.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2021 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.chassis.car.ui.sharedlibrary.appstyleview;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager.LayoutParams;
-import android.widget.ImageView;
-import android.widget.ScrollView;
-
-import com.android.car.ui.sharedlibrary.oemapis.appstyledview.AppStyledViewControllerOEMV1;
-
-import com.chassis.car.ui.sharedlibrary.R;
-
-/**
- * The OEM implementation for {@link AppStyledViewControllerOEMV1} for a AppStyledView.
- */
-public class AppStyleViewControllerImpl implements AppStyledViewControllerOEMV1 {
-
-    private final Context mContext;
-    private View mAppStyleView;
-    private int mNavIcon;
-    private Runnable mCloseListener = null;
-
-    public AppStyleViewControllerImpl(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public View getAppStyledView(View content) {
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mAppStyleView = inflater.inflate(R.layout.app_styled_view, null, false);
-
-        ScrollView scrollview = mAppStyleView.findViewById(R.id.app_styled_content);
-        scrollview.addView(content);
-
-        ImageView navIcon = mAppStyleView.findViewById(R.id.app_styled_view_icon_close);
-        if (mNavIcon == 0) {
-            navIcon.setImageResource(R.drawable.icon_back);
-        } else if (mNavIcon == 1) {
-            navIcon.setImageResource(R.drawable.icon_close);
-        } else {
-            navIcon.setImageResource(R.drawable.icon_close);
-        }
-
-        if (mCloseListener != null) {
-            navIcon.setOnClickListener((v) -> {
-                mCloseListener.run();
-            });
-        }
-
-        return mAppStyleView;
-    }
-
-    @Override
-    public void setOnCloseClickListener(Runnable listener) {
-        mCloseListener = listener;
-    }
-
-    @Override
-    public void setNavIcon(int navIcon) {
-        mNavIcon = navIcon;
-    }
-
-    @Override
-    public LayoutParams getDialogWindowLayoutParam(LayoutParams params) {
-        params.width = Math.round(
-                mContext.getResources().getDimension(R.dimen.app_styled_dialog_width));
-        params.height = Math.round(
-                mContext.getResources().getDimension(R.dimen.app_styled_dialog_height));
-        params.gravity = Gravity.TOP | Gravity.START;
-        params.x = Math.round(
-                mContext.getResources().getDimension(R.dimen.app_styled_dialog_position_x));
-        params.y = Math.round(
-                mContext.getResources().getDimension(R.dimen.app_styled_dialog_position_y));
-        return params;
-    }
-}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/RecyclerViewImpl.java b/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/RecyclerViewImpl.java
deleted file mode 100644
index f8c4d6a..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/recyclerview/RecyclerViewImpl.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2021 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.chassis.car.ui.sharedlibrary.recyclerview;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.AdapterOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.LayoutStyleOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.OnScrollListenerOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewAttributesOEMV1;
-import com.android.car.ui.sharedlibrary.oemapis.recyclerview.RecyclerViewOEMV1;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Reference OEM implementation for RecyclerView
- */
-public final class RecyclerViewImpl extends RecyclerView implements RecyclerViewOEMV1 {
-
-    @NonNull
-    private List<OnScrollListenerOEMV1> mScrollListeners = new ArrayList<>();
-
-    @NonNull
-    private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
-        @Override
-        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-            for (OnScrollListenerOEMV1 listener: mScrollListeners) {
-                listener.onScrolled((RecyclerViewOEMV1) recyclerView, dx, dy);
-            }
-        }
-
-        @Override
-        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-            for (OnScrollListenerOEMV1 listener: mScrollListeners) {
-                listener.onScrollStateChanged((RecyclerViewOEMV1) recyclerView, newState);
-            }
-        }
-    };
-
-    public RecyclerViewImpl(@NonNull Context context) {
-        super(context);
-    }
-
-    public RecyclerViewImpl(Context context, RecyclerViewAttributesOEMV1 attrs) {
-        super(context);
-        setLayoutStyle(attrs.getLayoutStyle());
-    }
-
-    @Override
-    public void setAdapter(AdapterOEMV1 adapterV1) {
-        if (adapterV1 == null) {
-            super.setAdapter(null);
-        } else {
-            super.setAdapter(new AdapterWrapper(adapterV1));
-        }
-    }
-
-    @Override
-    public void addOnScrollListener(OnScrollListenerOEMV1 listener) {
-        if (listener == null) {
-            return;
-        }
-        if (mScrollListeners.isEmpty()) {
-            super.addOnScrollListener(mOnScrollListener);
-        }
-        mScrollListeners.add(listener);
-    }
-
-    @Override
-    public void removeOnScrollListener(OnScrollListenerOEMV1 listener) {
-        if (listener == null) {
-            return;
-        }
-        mScrollListeners.remove(listener);
-        if (mScrollListeners.isEmpty()) {
-            super.removeOnScrollListener(mOnScrollListener);
-        }
-    }
-
-    @Override
-    public void clearOnScrollListeners() {
-        if (mScrollListeners != null) {
-            mScrollListeners.clear();
-            super.clearOnScrollListeners();
-        }
-    }
-
-    @Override
-    public void scrollToPosition(int position) {
-        super.scrollToPosition(position);
-    }
-
-    @Override
-    public void smoothScrollBy(int dx, int dy) {
-        super.smoothScrollBy(dx, dy);
-    }
-
-    @Override
-    public void smoothScrollToPosition(int position) {
-        super.smoothScrollToPosition(position);
-    }
-
-    @Override
-    public void setHasFixedSize(boolean hasFixedSize) {
-        super.setHasFixedSize(hasFixedSize);
-    }
-
-    @Override
-    public boolean hasFixedSize() {
-        return super.hasFixedSize();
-    }
-
-    @Override
-    public void setLayoutStyle(LayoutStyleOEMV1 layoutStyle) {
-        if (layoutStyle.getLayoutType() == LayoutStyleOEMV1.LAYOUT_TYPE_LINEAR) {
-            setLayoutManager(new LinearLayoutManager(getContext(),
-                    layoutStyle.getOrientation(),
-                    layoutStyle.getReverseLayout()));
-        } else {
-            setLayoutManager(new GridLayoutManager(getContext(),
-                    layoutStyle.getSpanCount(),
-                    layoutStyle.getOrientation(),
-                    layoutStyle.getReverseLayout()));
-            if (layoutStyle.getSpanSizeLookup() != null) {
-                ((GridLayoutManager) getLayoutManager()).setSpanSizeLookup(new SpanSizeLookup() {
-                    @Override
-                    public int getSpanSize(int position) {
-                        return layoutStyle.getSpanSizeLookup().getSpanSize(position);
-                    }
-                });
-            }
-        }
-    }
-
-    public View getView() {
-        return this;
-    }
-
-    @Override
-    public View getContainer() {
-        return this;
-    }
-}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OverflowMenuItem.java b/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OverflowMenuItem.java
deleted file mode 100644
index 301b235..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/java/com/chassis/car/ui/sharedlibrary/toolbar/OverflowMenuItem.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2021 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.chassis.car.ui.sharedlibrary.toolbar;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.car.ui.sharedlibrary.oemapis.toolbar.MenuItemOEMV1;
-
-import com.chassis.car.ui.sharedlibrary.R;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-class OverflowMenuItem implements MenuItemOEMV1 {
-
-    @NonNull
-    private final Context mSharedLibraryContext;
-
-    @NonNull
-    private final Context mActivityContext;
-
-    @NonNull
-    private List<? extends MenuItemOEMV1> mOverflowMenuItems = Collections.emptyList();
-
-    @Nullable
-    private Consumer<MenuItemOEMV1> mUpdateListener;
-
-    @Nullable
-    private Dialog mDialog;
-
-    OverflowMenuItem(
-            @NonNull Context sharedLibraryContext,
-            @NonNull Context activityContext) {
-        mSharedLibraryContext = sharedLibraryContext;
-        mActivityContext = activityContext;
-    }
-
-    public void setOverflowMenuItems(List<? extends MenuItemOEMV1> menuItems) {
-        mOverflowMenuItems = menuItems;
-
-        if (mDialog != null) {
-            mDialog.dismiss();
-            mDialog = null;
-        }
-
-        if (mUpdateListener != null) {
-            mUpdateListener.accept(this);
-        }
-    }
-
-    @Override
-    public void setUpdateListener(@Nullable Consumer<MenuItemOEMV1> listener) {
-        mUpdateListener = listener;
-    }
-
-    @Override
-    public void performClick() {
-        if (!isEnabled() || !isVisible()) {
-            return;
-        }
-
-        String[] titles = mOverflowMenuItems.stream()
-                .map(MenuItemOEMV1::getTitle)
-                .toArray(String[]::new);
-
-        mDialog = new AlertDialog.Builder(mActivityContext)
-                .setItems(titles, (dialog, which) -> {
-                    mOverflowMenuItems.get(which).performClick();
-                    dialog.dismiss();
-                }).create();
-        mDialog.show();
-    }
-
-    @Override
-    public int getId() {
-        return View.NO_ID;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return true;
-    }
-
-    @Override
-    public boolean isCheckable() {
-        return false;
-    }
-
-    @Override
-    public boolean isChecked() {
-        return false;
-    }
-
-    @Override
-    public boolean isTinted() {
-        return false;
-    }
-
-    @Override
-    public boolean isVisible() {
-        return mOverflowMenuItems.size() > 0;
-    }
-
-    @Override
-    public boolean isActivatable() {
-        return false;
-    }
-
-    @Override
-    public boolean isActivated() {
-        return false;
-    }
-
-    @Override
-    public String getTitle() {
-        return mSharedLibraryContext.getString(R.string.toolbar_menu_item_overflow_title);
-    }
-
-    @Override
-    public boolean isRestricted() {
-        return false;
-    }
-
-    @Override
-    public boolean isShowingIconAndTitle() {
-        return false;
-    }
-
-    @Override
-    public boolean isClickable() {
-        return true;
-    }
-
-    @Override
-    public int getDisplayBehavior() {
-        return MenuItemOEMV1.DISPLAY_BEHAVIOR_ALWAYS;
-    }
-
-    @Override
-    public Drawable getIcon() {
-        return mSharedLibraryContext.getDrawable(R.drawable.toolbar_menu_item_overflow);
-    }
-
-    @Override
-    public boolean isPrimary() {
-        return false;
-    }
-}
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/toolbar_tab.xml b/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/toolbar_tab.xml
deleted file mode 100644
index 8854de1..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/layout-port/toolbar_tab.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="match_parent"
-    android:layout_weight="1"
-    android:orientation="vertical"
-    android:paddingStart="12dp"
-    android:paddingEnd="12dp"
-    android:gravity="center"
-    android:background="?android:attr/selectableItemBackground">
-    <ImageView
-        android:id="@+id/car_ui_toolbar_tab_item_icon"
-        android:layout_width="36dp"
-        android:layout_height="36dp"
-        android:scaleType="fitCenter"
-        android:tint="@color/toolbar_tab_item_selector"
-        android:tintMode="src_in" />
-    <TextView
-        android:id="@+id/car_ui_toolbar_tab_item_text"
-        android:layout_width="135dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        android:gravity="center"
-        android:textAppearance="@style/TextAppearance.CarUi.Widget.Toolbar.Tab"/>
-</LinearLayout>
diff --git a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/strings.xml b/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/strings.xml
deleted file mode 100644
index 3e77a56..0000000
--- a/car-ui-lib/referencedesign/sharedlibrary/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-  <!-- Search hint, displayed inside the search box [CHAR LIMIT=50] -->
-  <string name="toolbar_default_search_hint">Search&#8230;</string>
-
-  <!-- The content description on the toolbar back button [CHAR_LIMIT=100] -->
-  <string name="toolbar_nav_icon_content_description">Back</string>
-
-  <!-- The content description of the toolber menu item overflow button [CHAR_LIMIT=50] -->
-  <string name="toolbar_menu_item_overflow_title">Overflow</string>
-</resources>
diff --git a/car-ui-lib/settings.gradle b/car-ui-lib/settings.gradle
index 43af7e0..df1489d 100644
--- a/car-ui-lib/settings.gradle
+++ b/car-ui-lib/settings.gradle
@@ -20,8 +20,8 @@
 project(':PaintBooth').projectDir = new File('./paintbooth')
 include ':oem-apis'
 project(':oem-apis').projectDir = new File('./oem-apis')
-include ':shared-library'
-project(':shared-library').projectDir = new File('./referencedesign/sharedlibrary')
+include ':plugin'
+project(':plugin').projectDir = new File('./referencedesign/plugin')
 
 rootProject.name='Chassis'
 include ':car-rotary-lib'
diff --git a/car-ui-lib/tests/apitest/auto-generate-resources.py b/car-ui-lib/tests/apitest/auto-generate-resources.py
index c48e04b..ad5f2f7 100755
--- a/car-ui-lib/tests/apitest/auto-generate-resources.py
+++ b/car-ui-lib/tests/apitest/auto-generate-resources.py
@@ -18,9 +18,10 @@
 import os
 import sys
 import re
-from resource_utils import get_all_resources, get_resources_from_single_file, add_resource_to_set, Resource
+from resource_utils import get_all_resources, get_resources_from_single_file, add_resource_to_set, Resource, merge_resources
 from git_utils import has_chassis_changes
 from datetime import datetime
+import urllib.request
 
 if sys.version_info[0] != 3:
     print("Must use python 3")
@@ -69,10 +70,14 @@
 
     resources = get_all_resources(os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res'))
     check_resource_names(resources, get_resources_from_single_file(os.path.join(OUTPUT_FILE_PATH, 'resource_name_allowed.xml')))
-
+    removed_resources = get_resources_from_single_file(os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res-overlayable/values/removed_resources.xml'))
     OVERLAYABLE_OUTPUT_FILE_PATH = os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res-overlayable/values/overlayable.xml')
     output_file = args.file or 'current.xml'
+
     if args.compare:
+        check_removed_resources(resources, removed_resources)
+        merge_resources(resources, removed_resources)
+
         old_mapping = get_resources_from_single_file(os.path.join(OUTPUT_FILE_PATH, 'current.xml'))
         compare_resources(old_mapping, resources, os.path.join(OUTPUT_FILE_PATH, 'current.xml'))
 
@@ -80,6 +85,7 @@
         add_constraintlayout_resources(resources)
         compare_resources(old_mapping, resources, OVERLAYABLE_OUTPUT_FILE_PATH)
     else:
+        merge_resources(resources, removed_resources)
         generate_current_file(resources, output_file)
         generate_overlayable_file(resources, OVERLAYABLE_OUTPUT_FILE_PATH)
 
@@ -120,7 +126,8 @@
     overlayable.set('name', 'car-ui-lib')
 
     policy = etree.SubElement(overlayable, 'policy')
-    policy.set('type', 'public')
+    # everything except public. source: frameworks/base/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h
+    policy.set('type', 'odm|oem|product|signature|system|vendor')
 
     for resource in resources:
         item = etree.SubElement(policy, 'item')
@@ -134,58 +141,36 @@
 
 def add_constraintlayout_resources(resources):
     # We need these to be able to use base layouts in RROs
-    # This should become unnecessary in S
-    # source: https://android.googlesource.com/platform/frameworks/opt/sherpa/+/studio-3.0/constraintlayout/src/main/res/values/attrs.xml
-    add_resource_to_set(resources, Resource('layout_optimizationLevel', 'attr'))
-    add_resource_to_set(resources, Resource('constraintSet', 'attr'))
-    add_resource_to_set(resources, Resource('barrierDirection', 'attr'))
-    add_resource_to_set(resources, Resource('constraint_referenced_ids', 'attr'))
-    add_resource_to_set(resources, Resource('chainUseRtl', 'attr'))
-    add_resource_to_set(resources, Resource('title', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintGuide_begin', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintGuide_end', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintGuide_percent', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintLeft_toLeftOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintLeft_toRightOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintRight_toLeftOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintRight_toRightOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintTop_toTopOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintTop_toBottomOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBottom_toTopOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBottom_toBottomOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBaseline_toBaselineOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintStart_toEndOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintStart_toStartOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintEnd_toStartOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintEnd_toEndOf', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginLeft', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginTop', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginRight', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginBottom', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginStart', 'attr'))
-    add_resource_to_set(resources, Resource('layout_goneMarginEnd', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHorizontal_bias', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintVertical_bias', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintWidth_default', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHeight_default', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintWidth_min', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintWidth_max', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintWidth_percent', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHeight_min', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHeight_max', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHeight_percent', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintLeft_creator', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintTop_creator', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintRight_creator', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBottom_creator', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintBaseline_creator', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintDimensionRatio', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHorizontal_weight', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintVertical_weight', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintHorizontal_chainStyle', 'attr'))
-    add_resource_to_set(resources, Resource('layout_constraintVertical_chainStyle', 'attr'))
-    add_resource_to_set(resources, Resource('layout_editor_absoluteX', 'attr'))
-    add_resource_to_set(resources, Resource('layout_editor_absoluteY', 'attr'))
+    # This should become unnecessary some time in future?
+    # Please keep this in-sync with res/raw/car_ui_keep.xml
+    url = "https://raw.githubusercontent.com/androidx/constraintlayout/main/constraintlayout/constraintlayout/src/main/res/values/attrs.xml"
+    import xml.etree.ElementTree as ET
+    tree = ET.parse(urllib.request.urlopen(url))
+    root = tree.getroot()
+
+    # The source here always points to the latest version of attrs.xml from androidx repo
+    # and since platform is not using the latest one, some of the tags should be excluded.
+    unsupported_attrs = [
+        "circularflow_radiusInDP",
+        "constraint_referenced_tags",
+        "layout_goneMarginBaseline",
+        "circularflow_angles",
+        "circularflow_defaultRadius",
+        "circularflow_defaultAngle",
+        "layout_marginBaseline",
+        "circularflow_viewCenter",
+        "layout_wrapBehaviorInParent",
+        "layout_constraintWidth",
+        "layout_constraintBaseline_toBottomOf",
+        "layout_constraintHeight",
+        "layout_constraintBaseline_toTopOf",
+    ]
+
+    attrs = root.findall("./declare-styleable[@name='ConstraintLayout_Layout']/attr")
+    for attr in attrs:
+      if "android:" not in attr.get('name') and attr.get('name') not in unsupported_attrs:
+        add_resource_to_set(resources, Resource(attr.get('name'), 'attr'))
+
 
 def check_resource_names(resources, allowed):
     newlist = resources.difference(allowed)
@@ -219,5 +204,10 @@
               "auto-generate-resources.py' again and submit the new %s" % res_public_file)
         sys.exit(1)
 
+def check_removed_resources(mapping, removed_resources):
+    intersection = removed_resources.intersection(mapping)
+    if len(intersection) > 0:
+        print('Usage of removed resources detected\n'+ '\n'.join(map(lambda x: str(x), intersection)))
+
 if __name__ == '__main__':
     main()
diff --git a/car-ui-lib/tests/apitest/current.xml b/car-ui-lib/tests/apitest/current.xml
index a34d2f5..3ed394c 100644
--- a/car-ui-lib/tests/apitest/current.xml
+++ b/car-ui-lib/tests/apitest/current.xml
@@ -3,10 +3,51 @@
 <resources>
   <public type="array" name="car_ui_ime_wide_screen_allowed_package_list"/>
   <public type="attr" name="CarUiToolbarStyle"/>
+  <public type="attr" name="actionEnabled"/>
+  <public type="attr" name="actionShown"/>
+  <public type="attr" name="activatable"/>
+  <public type="attr" name="activated"/>
+  <public type="attr" name="carUiActivity"/>
+  <public type="attr" name="carUiBaseLayout"/>
+  <public type="attr" name="carUiClickableWhileDisabled"/>
+  <public type="attr" name="carUiIcon"/>
+  <public type="attr" name="carUiLayout"/>
   <public type="attr" name="carUiPreferenceStyle"/>
   <public type="attr" name="carUiRecyclerViewStyle"/>
+  <public type="attr" name="carUiShowChevron"/>
+  <public type="attr" name="carUiSize"/>
+  <public type="attr" name="carUiToolbar"/>
+  <public type="attr" name="car_ui_ux_restricted"/>
+  <public type="attr" name="checkable"/>
+  <public type="attr" name="checked"/>
+  <public type="attr" name="displayBehavior"/>
+  <public type="attr" name="enableDivider"/>
+  <public type="attr" name="id"/>
+  <public type="attr" name="layoutManager"/>
+  <public type="attr" name="layoutStyle"/>
+  <public type="attr" name="logo"/>
+  <public type="attr" name="menuItems"/>
+  <public type="attr" name="numOfColumns"/>
+  <public type="attr" name="onClick"/>
   <public type="attr" name="preferenceStyle"/>
+  <public type="attr" name="reverseLayout"/>
+  <public type="attr" name="rotaryScrollEnabled"/>
+  <public type="attr" name="search"/>
+  <public type="attr" name="searchHint"/>
+  <public type="attr" name="secondaryActionIcon"/>
+  <public type="attr" name="secondaryActionStyle"/>
+  <public type="attr" name="secondaryActionText"/>
+  <public type="attr" name="settings"/>
+  <public type="attr" name="showBackground"/>
+  <public type="attr" name="showIconAndTitle"/>
+  <public type="attr" name="showMenuItemsWhileSearching"/>
+  <public type="attr" name="showTabsInSubpage"/>
   <public type="attr" name="state_ux_restricted"/>
+  <public type="attr" name="tinted"/>
+  <public type="attr" name="title"/>
+  <public type="attr" name="uxRestrictions"/>
+  <public type="attr" name="visible"/>
+  <public type="attr" name="widgetLayout"/>
   <public type="bool" name="car_ui_alert_dialog_force_dismiss_button"/>
   <public type="bool" name="car_ui_escrow_check_components_automatically"/>
   <public type="bool" name="car_ui_ime_wide_screen_aligned_left"/>
@@ -101,6 +142,10 @@
   <public type="dimen" name="car_ui_list_item_action_divider_width"/>
   <public type="dimen" name="car_ui_list_item_avatar_icon_height"/>
   <public type="dimen" name="car_ui_list_item_avatar_icon_width"/>
+  <public type="dimen" name="car_ui_list_item_check_box_end_inset"/>
+  <public type="dimen" name="car_ui_list_item_check_box_height"/>
+  <public type="dimen" name="car_ui_list_item_check_box_icon_container_width"/>
+  <public type="dimen" name="car_ui_list_item_check_box_start_inset"/>
   <public type="dimen" name="car_ui_list_item_content_icon_height"/>
   <public type="dimen" name="car_ui_list_item_content_icon_width"/>
   <public type="dimen" name="car_ui_list_item_end_inset"/>
@@ -109,6 +154,10 @@
   <public type="dimen" name="car_ui_list_item_height"/>
   <public type="dimen" name="car_ui_list_item_icon_container_width"/>
   <public type="dimen" name="car_ui_list_item_icon_size"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_end_inset"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_height"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_icon_container_width"/>
+  <public type="dimen" name="car_ui_list_item_radio_button_start_inset"/>
   <public type="dimen" name="car_ui_list_item_start_inset"/>
   <public type="dimen" name="car_ui_list_item_supplemental_icon_size"/>
   <public type="dimen" name="car_ui_list_item_text_no_icon_start_margin"/>
@@ -264,10 +313,12 @@
   <public type="id" name="car_ui_list_item_start_guideline"/>
   <public type="id" name="car_ui_list_item_supplemental_icon"/>
   <public type="id" name="car_ui_list_item_switch_widget"/>
+  <public type="id" name="car_ui_list_item_text_container"/>
   <public type="id" name="car_ui_list_item_title"/>
   <public type="id" name="car_ui_list_item_touch_interceptor"/>
   <public type="id" name="car_ui_list_limiting_message"/>
   <public type="id" name="car_ui_preference_container_without_widget"/>
+  <public type="id" name="car_ui_preference_fragment_container"/>
   <public type="id" name="car_ui_recycler_view"/>
   <public type="id" name="car_ui_scroll_bar"/>
   <public type="id" name="car_ui_scrollbar_page_down"/>
@@ -277,6 +328,7 @@
   <public type="id" name="car_ui_second_action_container"/>
   <public type="id" name="car_ui_secondary_action"/>
   <public type="id" name="car_ui_secondary_action_concrete"/>
+  <public type="id" name="car_ui_toolbar"/>
   <public type="id" name="car_ui_toolbar_background"/>
   <public type="id" name="car_ui_toolbar_bottom_guideline"/>
   <public type="id" name="car_ui_toolbar_bottom_styleable"/>
@@ -330,6 +382,7 @@
   <public type="id" name="spinner"/>
   <public type="id" name="textbox"/>
   <public type="id" name="title_template"/>
+  <public type="id" name="toolbar"/>
   <public type="integer" name="car_ui_default_max_string_length"/>
   <public type="integer" name="car_ui_scrollbar_longpress_initial_delay"/>
   <public type="integer" name="car_ui_scrollbar_longpress_repeat_interval"/>
@@ -338,18 +391,21 @@
   <public type="layout" name="car_ui_alert_dialog_title_with_subtitle"/>
   <public type="layout" name="car_ui_base_layout"/>
   <public type="layout" name="car_ui_base_layout_toolbar"/>
+  <public type="layout" name="car_ui_base_layout_toolbar_legacy"/>
   <public type="layout" name="car_ui_header_list_item"/>
   <public type="layout" name="car_ui_ims_wide_screen_input_view"/>
   <public type="layout" name="car_ui_list_item"/>
   <public type="layout" name="car_ui_list_item_compact"/>
   <public type="layout" name="car_ui_list_limiting_message"/>
   <public type="layout" name="car_ui_list_preference"/>
+  <public type="layout" name="car_ui_list_preference_with_toolbar"/>
   <public type="layout" name="car_ui_preference"/>
   <public type="layout" name="car_ui_preference_category"/>
   <public type="layout" name="car_ui_preference_chevron"/>
   <public type="layout" name="car_ui_preference_dialog_edittext"/>
   <public type="layout" name="car_ui_preference_dropdown"/>
   <public type="layout" name="car_ui_preference_fragment"/>
+  <public type="layout" name="car_ui_preference_fragment_with_toolbar"/>
   <public type="layout" name="car_ui_preference_two_action_icon"/>
   <public type="layout" name="car_ui_preference_two_action_switch"/>
   <public type="layout" name="car_ui_preference_two_action_text"/>
@@ -379,6 +435,7 @@
   <public type="string" name="car_ui_ellipsis"/>
   <public type="string" name="car_ui_ime_wide_screen_system_property_name"/>
   <public type="string" name="car_ui_installer_process_name"/>
+  <public type="string" name="car_ui_plugin_package_provider_authority_name"/>
   <public type="string" name="car_ui_preference_switch_off"/>
   <public type="string" name="car_ui_preference_switch_on"/>
   <public type="string" name="car_ui_restricted_while_driving"/>
@@ -386,13 +443,13 @@
   <public type="string" name="car_ui_scrollbar_page_down_button"/>
   <public type="string" name="car_ui_scrollbar_page_up_button"/>
   <public type="string" name="car_ui_scrolling_limited_message"/>
-  <public type="string" name="car_ui_shared_library_package_system_property_name"/>
   <public type="string" name="car_ui_toolbar_default_search_hint"/>
   <public type="string" name="car_ui_toolbar_menu_item_overflow_title"/>
   <public type="string" name="car_ui_toolbar_menu_item_search_title"/>
   <public type="string" name="car_ui_toolbar_menu_item_settings_title"/>
   <public type="string" name="car_ui_toolbar_nav_icon_content_description"/>
   <public type="style" name="CarUiPreferenceTheme"/>
+  <public type="style" name="CarUiPreferenceTheme.WithToolbar"/>
   <public type="style" name="Preference.CarUi"/>
   <public type="style" name="Preference.CarUi.Category"/>
   <public type="style" name="Preference.CarUi.CheckBoxPreference"/>
@@ -412,6 +469,7 @@
   <public type="style" name="Preference.CarUi.SeekBarPreference"/>
   <public type="style" name="Preference.CarUi.SwitchPreference"/>
   <public type="style" name="PreferenceFragment.CarUi"/>
+  <public type="style" name="PreferenceFragment.CarUi.WithToolbar"/>
   <public type="style" name="PreferenceFragmentList.CarUi"/>
   <public type="style" name="TextAppearance.CarUi"/>
   <public type="style" name="TextAppearance.CarUi.AlertDialog.Subtitle"/>
diff --git a/car-ui-lib/tests/apitest/resource_name_allowed.xml b/car-ui-lib/tests/apitest/resource_name_allowed.xml
index 733ca28..6d6e9a0 100644
--- a/car-ui-lib/tests/apitest/resource_name_allowed.xml
+++ b/car-ui-lib/tests/apitest/resource_name_allowed.xml
@@ -23,24 +23,58 @@
     The intention is to make it easier to maintain keep.xml
 -->
 <resources>
-    <item type="id" name="recycler_view"/>
+    <item type="attr" name="actionEnabled"/>
+    <item type="attr" name="actionShown"/>
+    <item type="attr" name="activatable"/>
+    <item type="attr" name="activated"/>
+    <item type="attr" name="car_ui_ux_restricted"/>
+    <item type="attr" name="checkable"/>
+    <item type="attr" name="checked"/>
+    <item type="attr" name="displayBehavior"/>
+    <item type="attr" name="enableDivider"/>
+    <item type="attr" name="id"/>
+    <item type="attr" name="layoutManager"/>
+    <item type="attr" name="layoutStyle"/>
+    <item type="attr" name="logo"/>
+    <item type="attr" name="menuItems"/>
+    <item type="attr" name="numOfColumns"/>
+    <item type="attr" name="onClick"/>
+    <item type="attr" name="preferenceStyle"/>
+    <item type="attr" name="reverseLayout"/>
+    <item type="attr" name="rotaryScrollEnabled"/>
+    <item type="attr" name="search"/>
+    <item type="attr" name="searchHint"/>
+    <item type="attr" name="secondaryActionIcon"/>
+    <item type="attr" name="secondaryActionStyle"/>
+    <item type="attr" name="secondaryActionText"/>
+    <item type="attr" name="settings"/>
+    <item type="attr" name="showBackground"/>
+    <item type="attr" name="showIconAndTitle"/>
+    <item type="attr" name="showMenuItemsWhileSearching"/>
+    <item type="attr" name="showTabsInSubpage"/>
     <item type="attr" name="state_ux_restricted"/>
-    <item type="id" name="radio_button"/>
-    <item type="id" name="spinner"/>
+    <item type="attr" name="tinted"/>
+    <item type="attr" name="title"/>
+    <item type="attr" name="uxRestrictions"/>
+    <item type="attr" name="visible"/>
+    <item type="attr" name="widgetLayout"/>
+    <item type="dimen" name="wrap_content"/>
+    <item type="id" name="action_widget_container"/>
+    <item type="id" name="container"/>
     <item type="id" name="list"/>
+    <item type="id" name="nested_recycler_view_layout"/>
+    <item type="id" name="radio_button"/>
+    <item type="id" name="search"/>
+    <item type="id" name="seek_bar"/>
     <item type="id" name="seek_bar_text_left"/>
     <item type="id" name="seek_bar_text_right"/>
-    <item type="id" name="nested_recycler_view_layout"/>
-    <item type="id" name="seek_bar"/>
-    <item type="id" name="textbox"/>
-    <item type="id" name="action_widget_container"/>
-    <item type="id" name="title_template"/>
+    <item type="id" name="seek_bar_text_top"/>
     <item type="id" name="seekbar"/>
     <item type="id" name="seekbar_value"/>
-    <item type="id" name="search"/>
+    <item type="id" name="spinner"/>
+    <item type="id" name="textbox"/>
+    <item type="id" name="title_template"/>
     <item type="id" name="toolbar"/>
-    <item type="id" name="container"/>
-    <item type="dimen" name="wrap_content"/>
-    <item type="id" name="seek_bar_text_top"/>
-    <item type="attr" name="preferenceStyle"/>
+    <item type="id" name="recycler_view"/>
+
 </resources>
diff --git a/car-ui-lib/tests/apitest/resource_utils.py b/car-ui-lib/tests/apitest/resource_utils.py
index fa5556a..0c81c40 100755
--- a/car-ui-lib/tests/apitest/resource_utils.py
+++ b/car-ui-lib/tests/apitest/resource_utils.py
@@ -104,6 +104,13 @@
     result = set()
     for resource in root:
         if resource.tag == 'declare-styleable' or resource.tag is etree.Comment:
+            attrs = resource.findall("attr")
+            for attr in attrs:
+                resName = attr.get('name')
+                resType = "attr"
+                if "android:" not in attr.get('name'):
+                    add_resource_to_set(result, Resource(resName, resType,
+                                                        ResourceLocation(filename, resource.sourceline)))
             continue
 
         resName = resource.get('name')
diff --git a/car-ui-lib/tests/rro-base/AndroidManifest.xml b/car-ui-lib/tests/rro-base/AndroidManifest.xml
index b6b6ffb..50ee476 100644
--- a/car-ui-lib/tests/rro-base/AndroidManifest.xml
+++ b/car-ui-lib/tests/rro-base/AndroidManifest.xml
@@ -25,7 +25,7 @@
         apk. Path and resource name should be same as the target apk in order to override.
         Look at car-ui-lib/res to see a list of resources available for customization.
     Step 3: Update Android.mk variables as needed (see details at generate-rros.mk):
-        CAR_UI_RRO_SET_NAME: general name of this overlay, e.g: base.
+        CAR_UI_RRO_SET_NAME: general name of this overlay, e.g.: base.
         CAR_UI_RESOURCE_DIR: location of the resources folder, e.g.: $(LOCAL_PATH)/res
         CAR_UI_RRO_TARGETS: list of package names to overlay
     Step 4: Build and generate the apk package for this project. Resulting RROs will be located at
diff --git a/car-ui-lib/tests/rro-base/res/values/styles.xml b/car-ui-lib/tests/rro-base/res/values/styles.xml
index b5d20db..17d263b 100644
--- a/car-ui-lib/tests/rro-base/res/values/styles.xml
+++ b/car-ui-lib/tests/rro-base/res/values/styles.xml
@@ -14,7 +14,8 @@
  limitations under the License.
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="TextAppearance.CarUi.Widget.Toolbar.Title" parent="android:TextAppearance.DeviceDefault">
+    <style name="TextAppearance.CarUi.Widget.Toolbar.Title"
+        parent="android:TextAppearance.DeviceDefault">
         <item name="android:singleLine">true</item>
         <item name="android:textSize">40sp</item>
         <item name="android:textColor">#FF00F0</item>
diff --git a/car-uxr-client-lib/Android.bp b/car-uxr-client-lib/Android.bp
index ca4fb09..531ae67 100644
--- a/car-uxr-client-lib/Android.bp
+++ b/car-uxr-client-lib/Android.bp
@@ -30,7 +30,10 @@
     libs: [
         "android.car-system-stubs",
     ],
-    sdk_version: "system_current",
+
+    min_sdk_version: "28", // P
+    target_sdk_version: "31",
+    sdk_version: "current",
 
     static_libs: [
         "androidx.recyclerview_recyclerview",
diff --git a/car-uxr-client-lib/build.gradle b/car-uxr-client-lib/build.gradle
new file mode 100644
index 0000000..f357a56
--- /dev/null
+++ b/car-uxr-client-lib/build.gradle
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// Library-level build file
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion gradle.ext.aaosLatestSDK
+
+    defaultConfig {
+        minSdkVersion 28
+        targetSdkVersion gradle.ext.aaosTargetSDK
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+        }
+    }
+
+    testOptions {
+        unitTests {
+            includeAndroidResources = true
+        }
+    }
+
+    useLibrary 'android.car'
+}
+
+dependencies {
+    def lifecycle_version = "2.2.0"
+    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+
+    implementation 'androidx.recyclerview:recyclerview:1.2.1'
+
+    implementation project(":car-ui-lib")
+}
diff --git a/certs/Android.bp b/certs/Android.bp
new file mode 100644
index 0000000..4bff55f
--- /dev/null
+++ b/certs/Android.bp
@@ -0,0 +1,8 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app_certificate {
+    name: "com-android-car-apps-test",
+    certificate: "com_android_car_apps_test"
+}
diff --git a/certs/README.md b/certs/README.md
new file mode 100644
index 0000000..4e450c4
--- /dev/null
+++ b/certs/README.md
@@ -0,0 +1,3 @@
+# Certs used for developing Car system Apps
+alias: carapps
+password: carapps
\ No newline at end of file
diff --git a/certs/com_android_car_apps_test.jks b/certs/com_android_car_apps_test.jks
new file mode 100644
index 0000000..5ac2ba1
--- /dev/null
+++ b/certs/com_android_car_apps_test.jks
Binary files differ
diff --git a/certs/com_android_car_apps_test.pk8 b/certs/com_android_car_apps_test.pk8
new file mode 100644
index 0000000..34f94c7
--- /dev/null
+++ b/certs/com_android_car_apps_test.pk8
Binary files differ
diff --git a/certs/com_android_car_apps_test.x509.pem b/certs/com_android_car_apps_test.x509.pem
new file mode 100644
index 0000000..1c98c13
--- /dev/null
+++ b/certs/com_android_car_apps_test.x509.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDeTCCAmGgAwIBAgIEeXV+cDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJV
+UzELMAkGA1UECBMCQ0ExDDAKBgNVBAcTA01UVjETMBEGA1UEChMKQXV0b21vdGl2
+ZTEaMBgGA1UECxMRU3lzdGVtIEV4cGVyaWVuY2UxEjAQBgNVBAMTCUFBT1MgQXBw
+czAeFw0yMTA4MTMyMTQxMzJaFw00NjA4MDcyMTQxMzJaMG0xCzAJBgNVBAYTAlVT
+MQswCQYDVQQIEwJDQTEMMAoGA1UEBxMDTVRWMRMwEQYDVQQKEwpBdXRvbW90aXZl
+MRowGAYDVQQLExFTeXN0ZW0gRXhwZXJpZW5jZTESMBAGA1UEAxMJQUFPUyBBcHBz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAj89/yYWwhOh+qCJVBThj
+skfcpxpTBHvVcd+S+ifiRAyetMAG330Ql4t0gHfEXdRa8hupwaIRAxTN3ipLhyfP
+9EpjfuSf1+lLSUZV7+S0Z98cMHAsQbu5jft9XU5GlQU/KzPDZJYo6A9Hilj4bbou
+PZcTEbsOZuXaKmGsJznVz97nGLQM8AJsVYuKXpho5rL/NRq3l1nSPiHfhWImVRcq
+qbgoTMmn1du4WtsqKhKTnew7ILx17qzYS83+C5WtI4WfoS7x/S3gLpfn56RhIKcT
+aGsxtQFEd4F4ERSXBTnaQJawOcxa7gwm3k8JacO6DvqoA96MekNPcS7EAdLoC2Mm
+8wIDAQABoyEwHzAdBgNVHQ4EFgQUaVopvD/66+afVc8Mj8RFe07dvrYwDQYJKoZI
+hvcNAQELBQADggEBAI0P9RUUFA0MwOmOgS9JknE5saAI+Pcut1kFYCwH22RypCpn
+GP9NJzxEFEDi03fMTRWhaSBkok2nEaWS5Io+eEpSQh9LJXVKpqilSOPev0VR/xQL
+9PYQ/OrmVY1XS5s56LsuKhDD80lSLhQhT2d+FdKQRKlzn3A9ZUA4s/ZIA8SUdJb/
+FKsDQPIDuaHiZG0/8h3kevyq8Zuh0HEQ0YVZzLXyu7d1/ShVjFp7DVs0Pd2MDbyR
+0i4JQSj2zAIhfqg+Wsxy4A2vxXk6LswCBJhouDBThHrIE6VVZoBBZeP03E+lVQaU
+3R1c5X48BOqSsy0m6bfFJkjfynnnYNl7/6GsDr0=
+-----END CERTIFICATE-----
diff --git a/encryption-runner/Android.bp b/encryption-runner/Android.bp
deleted file mode 100644
index 4e59864..0000000
--- a/encryption-runner/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2020 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "encryption-runner",
-    sdk_version: "current",
-    min_sdk_version: "28",
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
-    },
-    static_libs: [
-      "androidx.annotation_annotation",
-      "ukey2",
-    ],
-    srcs: [
-        "src/**/*.java",
-    ],
-    installable: true,
-}
diff --git a/encryption-runner/AndroidManifest.xml b/encryption-runner/AndroidManifest.xml
deleted file mode 100644
index 0d2f71e..0000000
--- a/encryption-runner/AndroidManifest.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="android.car.encryptionrunner" >
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
-</manifest>
diff --git a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
deleted file mode 100644
index 6a2b6d6..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunner.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import androidx.annotation.NonNull;
-
-/**
- * A generalized interface that allows for generating shared secrets as well as encrypting
- * messages.
- *
- * To use this interface:
- *
- * <p>1. As a client.
- *
- * {@code
- * HandshakeMessage initialClientMessage = clientRunner.initHandshake();
- * sendToServer(initialClientMessage.getNextMessage());
- * byte message = getServerResponse();
- * HandshakeMessage message = clientRunner.continueHandshake(message);
- * }
- *
- * <p>If it is a first-time connection,
- *
- * {@code message.getHandshakeState()} should be VERIFICATION_NEEDED, show user the verification
- * code and ask to verify.
- * After user confirmed, {@code HandshakeMessage lastMessage = clientRunner.verifyPin();} otherwise
- * {@code clientRunner.invalidPin(); }
- *
- * Use {@code lastMessage.getKey()} to get the key for encryption.
- *
- * <p>If it is a reconnection,
- *
- * {@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
- * send the authentication message over to server, then authenticate the message from server.
- *
- * {@code
- * clientMessage = clientRunner.initReconnectAuthentication(previousKey)
- * sendToServer(clientMessage.getNextMessage());
- * HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
- * }
- *
- * {@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
- *
- * <p>2. As a server.
- *
- * {@code
- * byte[] initialMessage = getClientMessageBytes();
- * HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
- * sendToClient(message.getNextMessage());
- * byte[] clientMessage = getClientResponse();
- * HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
- *
- * <p>if it is a first-time connection,
- *
- * {@code message.getHandshakeState()} should be VERIFICATION_NEEDED, show user the verification
- * code and ask to verify.
- * After PIN is confirmed, {@code HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
- * {@code clientRunner.invalidPin(); }
- * Use {@code lastMessage.getKey()} to get the key for encryption.
- *
- * <p>If it is a reconnection,
- *
- * {@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
- * waiting for client message.
- * After client message been received,
- * {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
- * sendToClient(serverMessage.getNextMessage());}
- * {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
- *
- * Also see {@link EncryptionRunnerTest} for examples.
- */
-public interface EncryptionRunner {
-
-    String TAG = "EncryptionRunner";
-
-    /**
-     * Starts an encryption handshake.
-     *
-     * @return A handshake message with information about the handshake that is started.
-     */
-    @NonNull
-    HandshakeMessage initHandshake();
-
-    /**
-     * Starts an encryption handshake where the device that is being communicated with already
-     * initiated the request.
-     *
-     * @param initializationRequest the bytes that the other device sent over.
-     * @return a handshake message with information about the handshake.
-     * @throws HandshakeException if initialization request is invalid.
-     */
-    @NonNull
-    HandshakeMessage respondToInitRequest(@NonNull byte[] initializationRequest)
-            throws HandshakeException;
-
-    /**
-     * Continues a handshake after receiving another response from the connected device.
-     *
-     * @param response the response from the other device.
-     * @return a message that can be used to continue the handshake.
-     * @throws HandshakeException if unexpected bytes in response.
-     */
-    @NonNull
-    HandshakeMessage continueHandshake(@NonNull byte[] response) throws HandshakeException;
-
-    /**
-     * Verifies the pin shown to the user. The result is the next handshake message and will
-     * typically contain an encryption key.
-     *
-     * @throws HandshakeException if not in state to verify pin.
-     */
-    @NonNull
-    HandshakeMessage verifyPin() throws HandshakeException;
-
-    /**
-     * Notifies the encryption runner that the user failed to validate the pin. After calling this
-     * method the runner should not be used, and will throw exceptions.
-     */
-    void invalidPin();
-
-    /**
-     * Verifies the reconnection message.
-     *
-     * <p>The message passed to this method should have been generated by
-     * {@link #initReconnectAuthentication(byte[] previousKey)}.
-     *
-     * <p>If the message is valid, then a {@link HandshakeMessage} will be returned that contains
-     * the encryption key and a handshake message which can be used to verify the other side of the
-     * connection.
-     *
-     * @param previousKey previously stored key.
-     * @param message     message from the client
-     * @return a handshake message with an encryption key if verification succeed.
-     * @throws HandshakeException if the message does not match.
-     */
-    @NonNull
-    HandshakeMessage authenticateReconnection(@NonNull byte[] message, @NonNull byte[] previousKey)
-            throws HandshakeException;
-
-    /**
-     * Initiates the reconnection verification by generating a message that should be sent to the
-     * device that is being reconnected to.
-     *
-     * @param previousKey previously stored key.
-     * @return a handshake message with client's message which will be sent to server.
-     * @throws HandshakeException when get encryption key's unique session fail.
-     */
-    @NonNull
-    HandshakeMessage initReconnectAuthentication(@NonNull byte[] previousKey)
-            throws HandshakeException;
-
-    /**
-     * De-serializes a previously serialized key generated by an instance of this encryption runner.
-     *
-     * @param serialized the serialized bytes of the key.
-     * @return the Key object used for encryption.
-     */
-    @NonNull
-    Key keyOf(@NonNull byte[] serialized);
-
-    /**
-     * Set the signal if it is a reconnection process.
-     *
-     * @param isReconnect {@code true} if it is a reconnect.
-     */
-    void setIsReconnect(boolean isReconnect);
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java b/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
deleted file mode 100644
index b479b50..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/EncryptionRunnerFactory.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * Factory that creates encryption runner.
- */
-public class EncryptionRunnerFactory {
-
-    private EncryptionRunnerFactory() {
-        // prevent instantiation.
-    }
-
-    @IntDef({EncryptionRunnerType.UKEY2, EncryptionRunnerType.OOB_UKEY2})
-    public @interface EncryptionRunnerType {
-        /** Use Ukey2 as underlying key exchange. */
-        int UKEY2 = 0;
-        /** Use Ukey2 and an out of band channel as underlying key exchange. */
-        int OOB_UKEY2 = 1;
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner} based on {@param type}.
-     */
-    public static EncryptionRunner newRunner(@EncryptionRunnerType int type) {
-        switch (type) {
-            case EncryptionRunnerType.UKEY2:
-                return new Ukey2EncryptionRunner();
-            case EncryptionRunnerType.OOB_UKEY2:
-                return new OobUkey2EncryptionRunner();
-            default:
-                throw new IllegalArgumentException("Unknown EncryptionRunnerType: " + type);
-        }
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner}.
-     *
-     * @deprecated Use {@link #newRunner(int)} instead.
-     */
-    @Deprecated
-    public static EncryptionRunner newRunner() {
-        return newRunner(EncryptionRunnerType.UKEY2);
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner} that doesn't actually do encryption but is useful
-     * for testing.
-     */
-    @VisibleForTesting
-    public static EncryptionRunner newFakeRunner() {
-        return new FakeEncryptionRunner();
-    }
-
-    /**
-     * Creates a new {@link EncryptionRunner} that doesn't actually do encryption but is useful
-     * for out of band association testing.
-     */
-    @VisibleForTesting
-    public static EncryptionRunner newOobFakeRunner() {
-        return new OobFakeEncryptionRunner();
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java
deleted file mode 100644
index 5fd1ff2..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/FakeEncryptionRunner.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * An encryption runner that doesn't actually do encryption. Useful for debugging. Do not use in
- * production environments.
- */
-@VisibleForTesting
-public class FakeEncryptionRunner implements EncryptionRunner {
-
-    private static final String KEY = "key";
-    private static final byte[] PLACEHOLDER_MESSAGE = "Placeholder Message".getBytes();
-    @VisibleForTesting
-    public static final String INIT = "init";
-    @VisibleForTesting
-    public static final String INIT_RESPONSE = "initResponse";
-    @VisibleForTesting
-    public static final String CLIENT_RESPONSE = "clientResponse";
-    public static final String VERIFICATION_CODE = "1234";
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
-    @interface Mode {
-
-        int UNKNOWN = 0;
-        int CLIENT = 1;
-        int SERVER = 2;
-    }
-
-    private boolean mIsReconnect;
-    private boolean mInitReconnectVerification;
-    private Key mCurrentFakeKey;
-    @Mode
-    private int mMode;
-    @HandshakeMessage.HandshakeState
-    private int mState;
-
-    @Override
-    public HandshakeMessage initHandshake() {
-        checkRunnerIsNew();
-        mMode = Mode.CLIENT;
-        mState = HandshakeMessage.HandshakeState.IN_PROGRESS;
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(mState)
-                .setNextMessage(INIT.getBytes())
-                .build();
-    }
-
-    @Override
-    public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
-            throws HandshakeException {
-        checkRunnerIsNew();
-        mMode = Mode.SERVER;
-        if (!new String(initializationRequest).equals(INIT)) {
-            throw new HandshakeException("Unexpected initialization request");
-        }
-        mState = HandshakeMessage.HandshakeState.IN_PROGRESS;
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(HandshakeMessage.HandshakeState.IN_PROGRESS)
-                .setNextMessage(INIT_RESPONSE.getBytes())
-                .build();
-    }
-
-    @Mode
-    protected int getMode() {
-        return mMode;
-    }
-
-    @HandshakeMessage.HandshakeState
-    protected int getState() {
-        return mState;
-    }
-
-    protected void setState(@HandshakeMessage.HandshakeState int state) {
-        mState = state;
-    }
-
-    private void checkRunnerIsNew() {
-        if (mState != HandshakeMessage.HandshakeState.UNKNOWN) {
-            throw new IllegalStateException("runner already initialized.");
-        }
-    }
-
-    @Override
-    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
-        if (mState != HandshakeMessage.HandshakeState.IN_PROGRESS) {
-            throw new HandshakeException("not waiting for response but got one");
-        }
-        switch (mMode) {
-            case Mode.SERVER:
-                if (!CLIENT_RESPONSE.equals(new String(response))) {
-                    throw new HandshakeException("unexpected response: " + new String(response));
-                }
-                mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
-                if (mIsReconnect) {
-                    verifyPin();
-                    mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
-                }
-                return HandshakeMessage.newBuilder()
-                        .setVerificationCode(VERIFICATION_CODE)
-                        .setHandshakeState(mState)
-                        .build();
-            case Mode.CLIENT:
-                if (!INIT_RESPONSE.equals(new String(response))) {
-                    throw new HandshakeException("unexpected response: " + new String(response));
-                }
-                mState = HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
-                if (mIsReconnect) {
-                    verifyPin();
-                    mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
-                }
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(mState)
-                        .setNextMessage(CLIENT_RESPONSE.getBytes())
-                        .setVerificationCode(VERIFICATION_CODE)
-                        .build();
-            default:
-                throw new IllegalStateException("unexpected role: " + mMode);
-        }
-    }
-
-    @Override
-    public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
-            throws HandshakeException {
-        mCurrentFakeKey = new FakeKey();
-        // Blindly verify the reconnection because this is a fake encryption runner.
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
-                .setKey(mCurrentFakeKey)
-                .setNextMessage(mInitReconnectVerification ? null : PLACEHOLDER_MESSAGE)
-                .build();
-    }
-
-    @Override
-    public HandshakeMessage initReconnectAuthentication(byte[] previousKey)
-            throws HandshakeException {
-        mInitReconnectVerification = true;
-        mState = HandshakeMessage.HandshakeState.RESUMING_SESSION;
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(mState)
-                .setNextMessage(PLACEHOLDER_MESSAGE)
-                .build();
-    }
-
-    @Override
-    public Key keyOf(byte[] serialized) {
-        return new FakeKey();
-    }
-
-    @Override
-    public HandshakeMessage verifyPin() throws HandshakeException {
-        if (mState != HandshakeMessage.HandshakeState.VERIFICATION_NEEDED) {
-            throw new IllegalStateException("asking to verify pin, state = " + mState);
-        }
-        mState = HandshakeMessage.HandshakeState.FINISHED;
-        return HandshakeMessage.newBuilder().setKey(new FakeKey()).setHandshakeState(
-                mState).build();
-    }
-
-    @Override
-    public void invalidPin() {
-        mState = HandshakeMessage.HandshakeState.INVALID;
-    }
-
-    @Override
-    public void setIsReconnect(boolean isReconnect) {
-        mIsReconnect = isReconnect;
-    }
-
-    /** Method to throw a HandshakeException for testing. */
-    public static void throwHandshakeException(String message) throws HandshakeException {
-        throw new HandshakeException(message);
-    }
-
-    class FakeKey implements Key {
-        @Override
-        public byte[] asBytes() {
-            return KEY.getBytes();
-        }
-
-        @Override
-        public byte[] encryptData(byte[] data) {
-            return data;
-        }
-
-        @Override
-        public byte[] decryptData(byte[] encryptedData) {
-            return encryptedData;
-        }
-
-        @Override
-        public byte[] getUniqueSession() {
-            return KEY.getBytes();
-        }
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/HandshakeException.java b/encryption-runner/src/android/car/encryptionrunner/HandshakeException.java
deleted file mode 100644
index 934a83a..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/HandshakeException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-/**
- * Exception indicating an error during a Handshake of EncryptionRunner.
- */
-public class HandshakeException extends Exception {
-
-    HandshakeException(String message) {
-        super(message);
-    }
-
-    HandshakeException(Exception e) {
-        super(e);
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java b/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
deleted file mode 100644
index a79c64e..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/HandshakeMessage.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import android.text.TextUtils;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * During an {@link EncryptionRunner} handshake process, these are the messages returned as part
- * of each step.
- */
-public class HandshakeMessage {
-
-    /**
-     * States for handshake progress.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({HandshakeState.UNKNOWN, HandshakeState.IN_PROGRESS, HandshakeState.VERIFICATION_NEEDED,
-            HandshakeState.FINISHED, HandshakeState.INVALID, HandshakeState.RESUMING_SESSION,
-            HandshakeState.OOB_VERIFICATION_NEEDED})
-    public @interface HandshakeState {
-        /**
-         * The initial state, this value is not expected to be returned.
-         */
-        int UNKNOWN = 0;
-        /**
-         * The handshake is in progress.
-         */
-        int IN_PROGRESS = 1;
-        /**
-         * The handshake is complete, but verification of the code is needed.
-         */
-        int VERIFICATION_NEEDED = 2;
-        /**
-         * The handshake is complete.
-         */
-        int FINISHED = 3;
-        /**
-         * The handshake is complete and not successful.
-         */
-        int INVALID = 4;
-        /**
-         * The handshake is complete, but extra verification is needed.
-         */
-        int RESUMING_SESSION = 5;
-        /**
-         * The handshake is complete, but out of band verification of the code is needed.
-         */
-        int OOB_VERIFICATION_NEEDED = 6;
-    }
-
-    @HandshakeState
-    private final int mHandshakeState;
-    private final Key mKey;
-    private final byte[] mNextMessage;
-    private final String mVerificationCode;
-    private final byte[] mOobVerificationCode;
-
-    /**
-     * @return Returns a builder for {@link HandshakeMessage}.
-     */
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
-    /**
-     * Use the builder;
-     */
-    private HandshakeMessage(
-            @HandshakeState int handshakeState,
-            @Nullable Key key,
-            @Nullable byte[] nextMessage,
-            @Nullable String verificationCode,
-            @Nullable byte[] oobVerificationCode) {
-        mHandshakeState = handshakeState;
-        mKey = key;
-        mNextMessage = nextMessage;
-        mVerificationCode = verificationCode;
-        mOobVerificationCode = oobVerificationCode;
-    }
-
-    /**
-     * Returns the next message to send in a handshake.
-     */
-    @Nullable
-    public byte[] getNextMessage() {
-        return mNextMessage == null ? null : mNextMessage.clone();
-    }
-
-    /**
-     * Returns the state of the handshake.
-     */
-    @HandshakeState
-    public int getHandshakeState() {
-        return mHandshakeState;
-    }
-
-    /**
-     * Returns the encryption key that can be used to encrypt data.
-     */
-    @Nullable
-    public Key getKey() {
-        return mKey;
-    }
-
-    /**
-     * Returns a verification code to show to the user.
-     */
-    @Nullable
-    public String getVerificationCode() {
-        return mVerificationCode;
-    }
-
-    /**
-     * Returns a verification code to be encrypted using an out-of-band key and sent to the remote
-     * device.
-     */
-    @Nullable
-    public byte[] getOobVerificationCode() {
-        return mOobVerificationCode;
-    }
-
-    static class Builder {
-        @HandshakeState
-        int mHandshakeState;
-        Key mKey;
-        byte[] mNextMessage;
-        String mVerificationCode;
-        byte[] mOobVerificationCode;
-
-        Builder setHandshakeState(@HandshakeState int handshakeState) {
-            mHandshakeState = handshakeState;
-            return this;
-        }
-
-        Builder setKey(@Nullable Key key) {
-            mKey = key;
-            return this;
-        }
-
-        Builder setNextMessage(@Nullable byte[] nextMessage) {
-            mNextMessage = nextMessage == null ? null : nextMessage.clone();
-            return this;
-        }
-
-        Builder setVerificationCode(@Nullable String verificationCode) {
-            mVerificationCode = verificationCode;
-            return this;
-        }
-
-        Builder setOobVerificationCode(@NonNull byte[] oobVerificationCode) {
-            mOobVerificationCode = oobVerificationCode;
-            return this;
-        }
-
-        HandshakeMessage build() {
-            if (mHandshakeState == HandshakeState.UNKNOWN) {
-                throw new IllegalStateException("must set handshake state before calling build");
-            }
-            if (mHandshakeState == HandshakeState.VERIFICATION_NEEDED
-                    && TextUtils.isEmpty(mVerificationCode)) {
-                throw new IllegalStateException(
-                        "State is verification needed, but verification code null.");
-            }
-            if (mHandshakeState == HandshakeState.OOB_VERIFICATION_NEEDED
-                    && (mOobVerificationCode == null || mOobVerificationCode.length == 0)) {
-                throw new IllegalStateException(
-                        "State is OOB verification needed, but OOB verification code null.");
-            }
-            return new HandshakeMessage(mHandshakeState, mKey, mNextMessage, mVerificationCode,
-                    mOobVerificationCode);
-        }
-
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/Key.java b/encryption-runner/src/android/car/encryptionrunner/Key.java
deleted file mode 100644
index e80be25..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/Key.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import androidx.annotation.NonNull;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-
-/**
- * Represents a serializable encryption key.
- */
-public interface Key {
-    /**
-     * Returns a serialized encryption key.
-     */
-    @NonNull
-    byte[] asBytes();
-
-    /**
-     * Encrypts data using this key.
-     *
-     * @param data the data to be encrypted
-     * @return the encrypted data.
-     */
-    @NonNull
-    byte[] encryptData(@NonNull byte[] data);
-
-    /**
-     * Decrypts data using this key.
-     *
-     * @param encryptedData The encrypted data.
-     * @return decrypted data.
-     * @throws SignatureException if encrypted data is not properly signed.
-     */
-    @NonNull
-    byte[] decryptData(@NonNull byte[] encryptedData) throws SignatureException;
-
-    /**
-     * Returns a cryptographic digest of the key.
-     *
-     * @throws NoSuchAlgorithmException when a unique session can not be created.
-     */
-    @NonNull
-    byte[] getUniqueSession() throws NoSuchAlgorithmException;
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java
deleted file mode 100644
index 3ab17d2..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/OobFakeEncryptionRunner.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-/**
- * An encryption runner that doesn't actually do encryption. Useful for debugging out of band
- * association. Do not use in production environments.
- */
-public class OobFakeEncryptionRunner extends FakeEncryptionRunner {
-
-    @Override
-    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
-        if (getState() != HandshakeMessage.HandshakeState.IN_PROGRESS) {
-            throw new HandshakeException("not waiting for response but got one");
-        }
-
-        @HandshakeMessage.HandshakeState int newState =
-                HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED;
-        switch (getMode()) {
-            case Mode.SERVER:
-                if (!CLIENT_RESPONSE.equals(new String(response))) {
-                    throw new HandshakeException("unexpected response: " + new String(response));
-                }
-                setState(newState);
-                return HandshakeMessage.newBuilder()
-                        .setOobVerificationCode(VERIFICATION_CODE.getBytes())
-                        .setHandshakeState(newState)
-                        .build();
-            case Mode.CLIENT:
-                if (!INIT_RESPONSE.equals(new String(response))) {
-                    throw new HandshakeException("unexpected response: " + new String(response));
-                }
-                setState(newState);
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(newState)
-                        .setNextMessage(CLIENT_RESPONSE.getBytes())
-                        .setOobVerificationCode(VERIFICATION_CODE.getBytes())
-                        .build();
-            default:
-                throw new IllegalStateException("unexpected role: " + getMode());
-        }
-    }
-
-    @Override
-    public HandshakeMessage verifyPin() throws HandshakeException {
-        @HandshakeMessage.HandshakeState int state = getState();
-        if (state != HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED) {
-            throw new IllegalStateException("asking to verify pin, state = " + state);
-        }
-        state = HandshakeMessage.HandshakeState.FINISHED;
-        return HandshakeMessage.newBuilder().setKey(new FakeKey()).setHandshakeState(
-                state).build();
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java
deleted file mode 100644
index 9474bd4..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/OobUkey2EncryptionRunner.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright 2020 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 android.car.encryptionrunner;
-
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
-
-/**
- * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation, and generates a
- * longer token for the out of band verification step.
- *
- * <p>To use this class:
- *
- * <p>1. As a client.
- *
- * <p>{@code
- * HandshakeMessage initialClientMessage = clientRunner.initHandshake();
- * sendToServer(initialClientMessage.getNextMessage());
- * byte message = getServerResponse();
- * HandshakeMessage message = clientRunner.continueHandshake(message);
- * }
- *
- * <p>If it is a first-time connection,
- *
- * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED. Wait for an encrypted
- * message sent from the server, and decrypt that message with an out of band key that was generated
- * before the start of the handshake.
- *
- * <p>After confirming that the decrypted message matches the verification code, send an encrypted
- * message back to the server, and call {@code HandshakeMessage lastMessage =
- * clientRunner.verifyPin();} otherwise {@code clientRunner.invalidPin(); }
- *
- * <p>Use {@code lastMessage.getKey()} to get the key for encryption.
- *
- * <p>If it is a reconnection,
- *
- * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION, PIN has been verified blindly,
- * send the authentication message over to server, then authenticate the message from server.
- *
- * <p>{@code
- * clientMessage = clientRunner.initReconnectAuthentication(previousKey)
- * sendToServer(clientMessage.getNextMessage());
- * HandshakeMessage lastMessage = clientRunner.authenticateReconnection(previousKey, message)
- * }
- *
- * <p>{@code lastMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
- *
- * <p>2. As a server.
- *
- * <p>{@code
- * byte[] initialMessage = getClientMessageBytes();
- * HandshakeMessage message = serverRunner.respondToInitRequest(initialMessage);
- * sendToClient(message.getNextMessage());
- * byte[] clientMessage = getClientResponse();
- * HandshakeMessage message = serverRunner.continueHandshake(clientMessage);}
- *
- * <p>if it is a first-time connection,
- *
- * <p>{@code message.getHandshakeState()} should be OOB_VERIFICATION_NEEDED, send the verification
- * code to the client, encrypted using an out of band key generated before the start of the
- * handshake, and wait for a response from the client.
- * If the decrypted message from the client matches the verification code, call {@code
- * HandshakeMessage lastMessage = serverRunner.verifyPin}, otherwise
- * {@code clientRunner.invalidPin(); }
- * Use {@code lastMessage.getKey()} to get the key for encryption.
- *
- * <p>If it is a reconnection,
- *
- * <p>{@code message.getHandshakeState()} should be RESUMING_SESSION,PIN has been verified blindly,
- * waiting for client message.
- * After client message been received,
- * {@code serverMessage = serverRunner.authenticateReconnection(previousKey, message);
- * sendToClient(serverMessage.getNextMessage());}
- * {@code serverMessage.getHandshakeState()} should be FINISHED if reconnection handshake is done.
- *
- * <p>Also see {@link EncryptionRunnerTest} for examples.
- */
-public final class OobUkey2EncryptionRunner extends Ukey2EncryptionRunner {
-    // Choose max verification string length supported by Ukey2
-    private static final int VERIFICATION_STRING_LENGTH = 32;
-
-    @Override
-    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
-        checkInitialized();
-
-        Ukey2Handshake uKey2Client = getUkey2Client();
-
-        try {
-            if (uKey2Client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) {
-                throw new IllegalStateException(
-                        "handshake is not in progress, state =" + uKey2Client.getHandshakeState());
-            }
-            uKey2Client.parseHandshakeMessage(response);
-
-            // Not obvious from ukey2 api, but getting the next message can change the state.
-            // calling getNext message might go from in progress to verification needed, on
-            // the assumption that we already send this message to the peer.
-            byte[] nextMessage = null;
-            if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
-                nextMessage = uKey2Client.getNextHandshakeMessage();
-            }
-
-            byte[] verificationCode = null;
-            if (uKey2Client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
-                // getVerificationString() needs to be called before notifyPinVerified().
-                verificationCode = uKey2Client.getVerificationString(VERIFICATION_STRING_LENGTH);
-                if (isReconnect()) {
-                    HandshakeMessage handshakeMessage = verifyPin();
-                    return HandshakeMessage.newBuilder()
-                            .setHandshakeState(handshakeMessage.getHandshakeState())
-                            .setNextMessage(nextMessage)
-                            .build();
-                }
-            }
-
-            return HandshakeMessage.newBuilder()
-                    .setHandshakeState(HandshakeMessage.HandshakeState.OOB_VERIFICATION_NEEDED)
-                    .setNextMessage(nextMessage)
-                    .setOobVerificationCode(verificationCode)
-                    .build();
-        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
-                | Ukey2Handshake.AlertException e) {
-            throw new HandshakeException(e);
-        }
-    }
-}
diff --git a/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java b/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
deleted file mode 100644
index ac1bf91..0000000
--- a/encryption-runner/src/android/car/encryptionrunner/Ukey2EncryptionRunner.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.car.encryptionrunner;
-
-import android.util.Log;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.google.security.cryptauth.lib.securegcm.D2DConnectionContext;
-import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake;
-import com.google.security.cryptauth.lib.securemessage.CryptoOps;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation.
- */
-public class Ukey2EncryptionRunner implements EncryptionRunner {
-
-    private static final Ukey2Handshake.HandshakeCipher CIPHER =
-            Ukey2Handshake.HandshakeCipher.P256_SHA512;
-    private static final int RESUME_HMAC_LENGTH = 32;
-    private static final byte[] RESUME = "RESUME".getBytes();
-    private static final byte[] SERVER = "SERVER".getBytes();
-    private static final byte[] CLIENT = "CLIENT".getBytes();
-    private static final int AUTH_STRING_LENGTH = 6;
-
-    @IntDef({Mode.UNKNOWN, Mode.CLIENT, Mode.SERVER})
-    private @interface Mode {
-        int UNKNOWN = 0;
-        int CLIENT = 1;
-        int SERVER = 2;
-    }
-
-    private Ukey2Handshake mUkey2client;
-    private boolean mRunnerIsInvalid;
-    private Key mCurrentKey;
-    private byte[] mCurrentUniqueSesion;
-    private byte[] mPrevUniqueSesion;
-    private boolean mIsReconnect;
-    private boolean mInitReconnectionVerification;
-    @Mode
-    private int mMode = Mode.UNKNOWN;
-
-    @Override
-    public HandshakeMessage initHandshake() {
-        checkRunnerIsNew();
-        mMode = Mode.CLIENT;
-        try {
-            mUkey2client = Ukey2Handshake.forInitiator(CIPHER);
-            return HandshakeMessage.newBuilder()
-                    .setHandshakeState(getHandshakeState())
-                    .setNextMessage(mUkey2client.getNextHandshakeMessage())
-                    .build();
-        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException e) {
-            Log.e(TAG, "unexpected exception", e);
-            throw new RuntimeException(e);
-        }
-
-    }
-
-    @Override
-    public void setIsReconnect(boolean isReconnect) {
-        mIsReconnect = isReconnect;
-    }
-
-    @Override
-    public HandshakeMessage respondToInitRequest(byte[] initializationRequest)
-            throws HandshakeException {
-        checkRunnerIsNew();
-        mMode = Mode.SERVER;
-        try {
-            if (mUkey2client != null) {
-                throw new IllegalStateException("Cannot reuse encryption runners, "
-                        + "this one is already initialized");
-            }
-            mUkey2client = Ukey2Handshake.forResponder(CIPHER);
-            mUkey2client.parseHandshakeMessage(initializationRequest);
-            return HandshakeMessage.newBuilder()
-                    .setHandshakeState(getHandshakeState())
-                    .setNextMessage(mUkey2client.getNextHandshakeMessage())
-                    .build();
-
-        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
-                | Ukey2Handshake.AlertException e) {
-            throw new HandshakeException(e);
-        }
-    }
-
-    private void checkRunnerIsNew() {
-        if (mUkey2client != null) {
-            throw new IllegalStateException("This runner is already initialized.");
-        }
-    }
-
-
-    @Override
-    public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException {
-        checkInitialized();
-        try {
-            if (mUkey2client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) {
-                throw new IllegalStateException("handshake is not in progress, state ="
-                        + mUkey2client.getHandshakeState());
-            }
-            mUkey2client.parseHandshakeMessage(response);
-
-            // Not obvious from ukey2 api, but getting the next message can change the state.
-            // calling getNext message might go from in progress to verification needed, on
-            // the assumption that we already send this message to the peer.
-            byte[] nextMessage = null;
-            if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) {
-                nextMessage = mUkey2client.getNextHandshakeMessage();
-            }
-            String verificationCode = null;
-            if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) {
-                // getVerificationString() needs to be called before verifyPin().
-                verificationCode = generateReadablePairingCode(
-                        mUkey2client.getVerificationString(AUTH_STRING_LENGTH));
-                if (mIsReconnect) {
-                    HandshakeMessage handshakeMessage = verifyPin();
-                    return HandshakeMessage.newBuilder()
-                            .setHandshakeState(handshakeMessage.getHandshakeState())
-                            .setNextMessage(nextMessage)
-                            .build();
-                }
-            }
-            return HandshakeMessage.newBuilder()
-                    .setHandshakeState(getHandshakeState())
-                    .setNextMessage(nextMessage)
-                    .setVerificationCode(verificationCode)
-                    .build();
-        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException
-                | Ukey2Handshake.AlertException e) {
-            throw new HandshakeException(e);
-        }
-    }
-
-    /**
-     * Returns a human-readable pairing code string generated from the verification bytes. Converts
-     * each byte into a digit with a simple modulo.
-     *
-     * <p>This should match the implementation in the iOS and Android client libraries.
-     */
-    @VisibleForTesting
-    String generateReadablePairingCode(byte[] verificationCode) {
-        StringBuilder outString = new StringBuilder();
-        for (byte b : verificationCode) {
-            int unsignedInt = Byte.toUnsignedInt(b);
-            int digit = unsignedInt % 10;
-            outString.append(digit);
-        }
-
-        return outString.toString();
-    }
-
-    private static class UKey2Key implements Key {
-
-        private final D2DConnectionContext mConnectionContext;
-
-        UKey2Key(@NonNull D2DConnectionContext connectionContext) {
-            this.mConnectionContext = connectionContext;
-        }
-
-        @Override
-        public byte[] asBytes() {
-            return mConnectionContext.saveSession();
-        }
-
-        @Override
-        public byte[] encryptData(byte[] data) {
-            return mConnectionContext.encodeMessageToPeer(data);
-        }
-
-        @Override
-        public byte[] decryptData(byte[] encryptedData) throws SignatureException {
-            return mConnectionContext.decodeMessageFromPeer(encryptedData);
-        }
-
-        @Override
-        public byte[] getUniqueSession() throws NoSuchAlgorithmException {
-            return mConnectionContext.getSessionUnique();
-        }
-    }
-
-    @Override
-    public HandshakeMessage verifyPin() throws HandshakeException {
-        checkInitialized();
-        mUkey2client.verifyHandshake();
-        int state = getHandshakeState();
-        try {
-            mCurrentKey = new UKey2Key(mUkey2client.toConnectionContext());
-        } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException e) {
-            throw new HandshakeException(e);
-        }
-        return HandshakeMessage.newBuilder()
-                .setHandshakeState(state)
-                .setKey(mCurrentKey)
-                .build();
-    }
-
-    /**
-     * <p>After getting message from the other device, authenticate the message with the previous
-     * stored key.
-     *
-     * If current device inits the reconnection authentication by calling {@code
-     * initReconnectAuthentication} and sends the message to the other device, the other device
-     * will call {@code authenticateReconnection()} with the received message and send its own
-     * message back to the init device. The init device will call {@code
-     * authenticateReconnection()} on the received message, but do not need to set the next
-     * message.
-     */
-    @Override
-    public HandshakeMessage authenticateReconnection(byte[] message, byte[] previousKey)
-            throws HandshakeException {
-        if (!mIsReconnect) {
-            throw new HandshakeException(
-                    "Reconnection authentication requires setIsReconnect(true)");
-        }
-        if (mCurrentKey == null) {
-            throw new HandshakeException("Current key is null, make sure verifyPin() is called.");
-        }
-        if (message.length != RESUME_HMAC_LENGTH) {
-            mRunnerIsInvalid = true;
-            throw new HandshakeException("Failing because (message.length =" + message.length
-                    + ") is not equal to " + RESUME_HMAC_LENGTH);
-        }
-        try {
-            mCurrentUniqueSesion = mCurrentKey.getUniqueSession();
-            mPrevUniqueSesion = keyOf(previousKey).getUniqueSession();
-        } catch (NoSuchAlgorithmException e) {
-            throw new HandshakeException(e);
-        }
-        switch (mMode) {
-            case Mode.SERVER:
-                if (!MessageDigest.isEqual(
-                        message, computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))) {
-                    mRunnerIsInvalid = true;
-                    throw new HandshakeException("Reconnection authentication failed.");
-                }
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
-                        .setKey(mCurrentKey)
-                        .setNextMessage(mInitReconnectionVerification ? null
-                                : computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))
-                        .build();
-            case Mode.CLIENT:
-                if (!MessageDigest.isEqual(
-                        message, computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))) {
-                    mRunnerIsInvalid = true;
-                    throw new HandshakeException("Reconnection authentication failed.");
-                }
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(HandshakeMessage.HandshakeState.FINISHED)
-                        .setKey(mCurrentKey)
-                        .setNextMessage(mInitReconnectionVerification ? null
-                                : computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))
-                        .build();
-            default:
-                throw new IllegalStateException(
-                        "Encountered unexpected role during authenticateReconnection: " + mMode);
-        }
-    }
-
-    /**
-     * Both client and server can call this method to send authentication message to the other
-     * device.
-     */
-    @Override
-    public HandshakeMessage initReconnectAuthentication(byte[] previousKey)
-            throws HandshakeException {
-        if (!mIsReconnect) {
-            throw new HandshakeException(
-                    "Reconnection authentication requires setIsReconnect(true).");
-        }
-        if (mCurrentKey == null) {
-            throw new HandshakeException("Current key is null, make sure verifyPin() is called.");
-        }
-        mInitReconnectionVerification = true;
-        try {
-            mCurrentUniqueSesion = mCurrentKey.getUniqueSession();
-            mPrevUniqueSesion = keyOf(previousKey).getUniqueSession();
-        } catch (NoSuchAlgorithmException e) {
-            throw new HandshakeException(e);
-        }
-        switch (mMode) {
-            case Mode.SERVER:
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(HandshakeMessage.HandshakeState.RESUMING_SESSION)
-                        .setNextMessage(computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, SERVER))
-                        .build();
-            case Mode.CLIENT:
-                return HandshakeMessage.newBuilder()
-                        .setHandshakeState(HandshakeMessage.HandshakeState.RESUMING_SESSION)
-                        .setNextMessage(computeMAC(mPrevUniqueSesion, mCurrentUniqueSesion, CLIENT))
-                        .build();
-            default:
-                throw new IllegalStateException(
-                        "Encountered unexpected role during authenticateReconnection: " + mMode);
-        }
-    }
-
-    protected final Ukey2Handshake getUkey2Client() {
-        return mUkey2client;
-    }
-
-    protected final boolean isReconnect() {
-        return mIsReconnect;
-    }
-
-    @HandshakeMessage.HandshakeState
-    private int getHandshakeState() {
-        checkInitialized();
-        switch (mUkey2client.getHandshakeState()) {
-            case ALREADY_USED:
-            case ERROR:
-                throw new IllegalStateException("unexpected error state");
-            case FINISHED:
-                if (mIsReconnect) {
-                    return HandshakeMessage.HandshakeState.RESUMING_SESSION;
-                }
-                return HandshakeMessage.HandshakeState.FINISHED;
-            case IN_PROGRESS:
-                return HandshakeMessage.HandshakeState.IN_PROGRESS;
-            case VERIFICATION_IN_PROGRESS:
-            case VERIFICATION_NEEDED:
-                return HandshakeMessage.HandshakeState.VERIFICATION_NEEDED;
-            default:
-                throw new IllegalStateException("unexpected handshake state");
-        }
-    }
-
-    @Override
-    public Key keyOf(byte[] serialized) {
-        return new UKey2Key(D2DConnectionContext.fromSavedSession(serialized));
-    }
-
-    @Override
-    public void invalidPin() {
-        mRunnerIsInvalid = true;
-    }
-
-    private UKey2Key checkIsUkey2Key(Key key) {
-        if (!(key instanceof UKey2Key)) {
-            throw new IllegalArgumentException("wrong key type");
-        }
-        return (UKey2Key) key;
-    }
-
-    protected void checkInitialized() {
-        if (mUkey2client == null) {
-            throw new IllegalStateException("runner not initialized");
-        }
-        if (mRunnerIsInvalid) {
-            throw new IllegalStateException("runner has been invalidated");
-        }
-    }
-
-    @Nullable
-    private byte[] computeMAC(byte[] previous, byte[] next, byte[] info) {
-        try {
-            SecretKeySpec inputKeyMaterial = new SecretKeySpec(
-                    concatByteArrays(previous, next), "" /* key type is just plain raw bytes */);
-            return CryptoOps.hkdf(inputKeyMaterial, RESUME, info);
-        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-            // Does not happen in practice
-            Log.e(TAG, "Compute MAC failed");
-            return null;
-        }
-    }
-
-    private static byte[] concatByteArrays(@NonNull byte[] a, @NonNull byte[] b) {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        try {
-            outputStream.write(a);
-            outputStream.write(b);
-        } catch (IOException e) {
-            return new byte[0];
-        }
-        return outputStream.toByteArray();
-    }
-}
diff --git a/specs/keylines.csv b/specs/keylines.csv
deleted file mode 100644
index 45b98d1..0000000
--- a/specs/keylines.csv
+++ /dev/null
@@ -1,15 +0,0 @@
-# Specifications for keylines.
-# * Any line beginning with a # will be ignored.
-# * Empty lines don't matter.
-# * Whitespace does not matter, commas are the delimiter. Don't skip commas, even if there is no
-#   value in the cell. While it is easy to modify the script to handle a skipped comma, it is better
-#   to enforce and error check rather than silently produce incorrect output.
-# * Don't put a comma after the last value, if there is nothing in the last cell, leave it empty.
-# * If a cell is left empty, the value for that alternate resource will get skipped. In that
-#   case Android will use the best matching rule to determine the value. The matching rules
-#   are specified at: https://developer.android.com/guide/topics/resources/providing-resources.html
-
-name,       none,   w480dp,     w720dp,     w1024dp,    w1024dp-port,   w1280dp,    w1920dp
-keyline_1,  24dp,   ,           ,           32dp,       ,               ,           48dp
-keyline_2,  96dp,   ,           104dp,      120dp,      140dp,          ,
-keyline_3,  112dp,  ,           ,           128dp,      ,               ,           152dp